From 12549df65c8b5874282babed9ff7955f855f1f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Wed, 22 Apr 2026 15:58:41 +0200 Subject: [PATCH 01/17] :tada: Add UI for webgl rendering setting (under config flag) --- common/src/app/common/flags.cljc | 1 + .../src/app/main/ui/settings/options.cljs | 35 ++++++++++-- .../src/app/main/ui/settings/options.scss | 56 +++++++++++++++++++ .../src/app/main/ui/workspace/main_menu.cljs | 20 ++++++- frontend/translations/en.po | 27 +++++++++ frontend/translations/es.po | 28 ++++++++++ 6 files changed, 161 insertions(+), 6 deletions(-) diff --git a/common/src/app/common/flags.cljc b/common/src/app/common/flags.cljc index d2232b4439..db4bb2731e 100644 --- a/common/src/app/common/flags.cljc +++ b/common/src/app/common/flags.cljc @@ -138,6 +138,7 @@ :render-wasm-dpr ;; Show WASM renderer info label (hidden by default). :render-wasm-info + :render-switch :hide-release-modal :subscriptions :subscriptions-old diff --git a/frontend/src/app/main/ui/settings/options.cljs b/frontend/src/app/main/ui/settings/options.cljs index fc5e9e14af..2e4f48d3ba 100644 --- a/frontend/src/app/main/ui/settings/options.cljs +++ b/frontend/src/app/main/ui/settings/options.cljs @@ -7,16 +7,24 @@ (ns app.main.ui.settings.options (:require-macros [app.main.style :as stl]) (:require + [app.config :as cf] [app.main.data.notifications :as ntf] [app.main.data.profile :as du] [app.main.refs :as refs] + [app.main.router :as rt] [app.main.store :as st] [app.main.ui.components.forms :as fm] + [app.main.ui.ds.controls.switch :refer [switch*]] + [app.main.ui.ds.foundations.assets.icon :refer [icon*]] + [app.main.ui.ds.foundations.typography :as t] + [app.main.ui.ds.foundations.typography.heading :refer [heading*]] + [app.main.ui.ds.foundations.typography.text :refer [text*]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.theme :as theme] [rumext.v2 :as mf])) + (def ^:private schema:options-form [:map {:title "OptionsForm"} [:lang {:optional true} [:string {:max 20}]] @@ -75,7 +83,23 @@ :data-testid "submit-lang-change" :class (stl/css :btn-primary)}]])) -;; --- Password Page +(defn ^:private go-settings-feedback + [event] + (dom/prevent-default event) + (st/emit! (rt/nav :settings-feedback))) + +(mf/defc webgl-settings* + [{:keys [is-render-enabled]}] + [:section {:class (stl/css :webgl-container)} + [:header {:class (stl/css :webgl-header)} + [:> heading* {:class (stl/css :title) :level 2 :typography t/title-large} (tr "dashboard.webgl-switch.title")] + [:> text* {:as "span" :class (stl/css :beta) :typography t/body-small} (tr "dashboard.webgl-switch.beta")]] + [:> text* {:class (stl/css :description) :typography t/body-medium} (tr "dashboard.webgl-switch.description")] + [:form {:class (stl/css :webgl-form)} + [:> heading* {:level 3 :typography t/headline-small} (tr "dashboard.webgl-switch.status")] + [:> switch* {:label (if is-render-enabled (tr "dashboard.webgl-switch.enabled") (tr "dashboard.webgl-switch.disabled")) + :default-checked is-render-enabled}]] + [:> text* {:typography t/body-medium :class (stl/css :feedback)} [:a {:href "#" :on-click go-settings-feedback :class (stl/css :link)} (tr "dashboard.webgl-switch.feedback") [:> icon* {:icon-id "arrow-up-right" :size "s"}]]]]) (mf/defc options-page [] @@ -83,7 +107,10 @@ #(dom/set-html-title (tr "title.settings.options"))) [:div {:class (stl/css :dashboard-settings)} - [:div {:class (stl/css :form-container) :data-testid "settings-form"} - [:h2 (tr "labels.settings")] - [:& options-form {}]]]) + [:* + [:div {:class (stl/css :form-container) :data-testid "settings-form"} + [:h2 (tr "labels.settings")] + [:& options-form {}]] + (when (contains? cf/flags :render-switch) + [:> webgl-settings* {:is-render-enabled true}])]]) diff --git a/frontend/src/app/main/ui/settings/options.scss b/frontend/src/app/main/ui/settings/options.scss index 474e96838f..2df1d9235f 100644 --- a/frontend/src/app/main/ui/settings/options.scss +++ b/frontend/src/app/main/ui/settings/options.scss @@ -5,3 +5,59 @@ // Copyright (c) KALEIDOS INC @use "./profile" as *; +@use "ds/_sizes.scss" as *; +@use "ds/_borders.scss" as *; + +.dashboard-settings { + display: grid; +} + +.form-container { + &:first-child { + margin-block-end: var(--sp-xxxl); + } +} + +/* Copied from profile.scss .form-container, but without included nested + rules, since we want custom styles for it */ +.webgl-container { + display: grid; + grid-auto-rows: auto; + gap: var(--sp-s); + width: $sz-500; + margin-block-start: var(--sp-xxxl); + padding-block-start: var(--sp-xxxl); + margin-block-end: $sz-120; /* FIXME: this should be a proper token */ + border-block-start: $b-1 solid var(--color-background-quaternary); + color: var(--color-foreground-primary); +} + +.webgl-header { + display: flex; + flex-direction: row; + align-items: baseline; + gap: var(--sp-m); +} + +.description { + color: var(--color-foreground-secondary); +} + +.beta { + color: var(--color-accent-primary); + padding: var(--sp-xxs) var(--sp-s); + border: $b-1 solid var(--color-accent-primary); + border-radius: $br-4; +} + +.title { + color: var(--color-foreground-primary); +} + +.link { + color: var(--color-accent-primary); + text-decoration: none; + display: flex; + align-items: center; + gap: var(--sp-xs); +} diff --git a/frontend/src/app/main/ui/workspace/main_menu.cljs b/frontend/src/app/main/ui/workspace/main_menu.cljs index 28e6f6722c..8e1fb5ea2a 100644 --- a/frontend/src/app/main/ui/workspace/main_menu.cljs +++ b/frontend/src/app/main/ui/workspace/main_menu.cljs @@ -226,7 +226,7 @@ (mf/defc preferences-menu* {::mf/private true ::mf/wrap [mf/memo]} - [{:keys [layout profile toggle-flag on-close toggle-theme]}] + [{:keys [layout profile toggle-flag on-close toggle-theme toggle-render]}] (let [show-nudge-options (mf/use-fn #(modal/show! {:type :nudge-option}))] @@ -321,7 +321,15 @@ "light" (tr "workspace.header.menu.toggle-system-theme") "system" (tr "workspace.header.menu.toggle-dark-theme") (tr "workspace.header.menu.toggle-light-theme"))] - [:> shortcuts* {:id :toggle-theme}]]])) + [:> shortcuts* {:id :toggle-theme}]] + (when (contains? cf/flags :render-switch) + [:> dropdown-menu-item* {:on-click toggle-render + :class (stl/css :base-menu-item :submenu-item) + :on-key-down (fn [event] + (when (kbd/enter? event) + (toggle-render event)))} + [:span {:class (stl/css :item-name)} + (tr "workspace.header.menu.enable-webgl")]])])) (mf/defc view-menu* {::mf/private true @@ -889,6 +897,13 @@ (dom/stop-propagation event) (st/emit! (du/toggle-theme)))) + toggle-render + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (js/console.log "toggle-render") + #_(st/emit! (toggle-render)))) + open-plugins-manager (mf/use-fn (fn [event] @@ -1068,6 +1083,7 @@ :profile profile :toggle-flag toggle-flag :toggle-theme toggle-theme + :toggle-render toggle-render :on-close close-sub-menu}] :plugins diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 45b1efd225..ec3fa004a6 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1159,6 +1159,27 @@ msgstr "Type to search results" msgid "dashboard.unpublish-shared" msgstr "Unpublish Library" +msgid "dashboard.webgl-switch.title" +msgstr "WebGL rendering" + +msgid "dashboard.webgl-switch.beta" +msgstr "Beta" + +msgid "dashboard.webgl-switch.description" +msgstr "WebGL rendering can improve performance, but it is in beta and may be less stable. Some visual differences may appear between the canvas, exports (SVG/​​PDF), and view mode." + +msgid "dashboard.webgl-switch.status" +msgstr "Status" + +msgid "dashboard.webgl-switch.enabled" +msgstr "Enabled" + +msgid "dashboard.webgl-switch.disabled" +msgstr "Disabled" + +msgid "dashboard.webgl-switch.feedback" +msgstr "Give feedback" + #:src/app/main/ui/workspace/tokens/import_from_library.cljs msgid "modals.import-library-tokens.title" msgstr "Import tokens from library?" @@ -5842,6 +5863,12 @@ msgstr "Switch to light theme" msgid "workspace.header.menu.toggle-system-theme" msgstr "Switch to system theme" +msgid "workspace.header.menu.enable-webgl" +msgstr "Enable WebGL rendering (beta)" + +msgid "workspace.header.menu.disable-webgl" +msgstr "Disable WebGL rendering (beta)" + #: src/app/main/ui/workspace/main_menu.cljs:492 msgid "workspace.header.menu.undo" msgstr "Undo" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 9314ec02d2..4aecc40b97 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1234,6 +1234,27 @@ msgstr "Tu nombre" msgid "dashboard.your-penpot" msgstr "Tu Penpot" +msgid "dashboard.webgl-switch.title" +msgstr "Renderizado WebGL" + +msgid "dashboard.webgl-switch.beta" +msgstr "Beta" + +msgid "dashboard.webgl-switch.description" +msgstr "El renderizado WebGL puede mejorar el rendimiento, pero está en beta y puede ser menos estable. Pueden aparecer diferencias visuales entre el lienzo, las exportaciones (SVG/PDF) y el modo de vista." + +msgid "dashboard.webgl-switch.status" +msgstr "Estado" + +msgid "dashboard.webgl-switch.enabled" +msgstr "Activado" + +msgid "dashboard.webgl-switch.disabled" +msgstr "Desactivado" + +msgid "dashboard.webgl-switch.feedback" +msgstr "Enviar comentarios" + #: src/app/main/ui/alert.cljs:35 msgid "ds.alert-ok" msgstr "Ok" @@ -5800,6 +5821,13 @@ msgstr "Cambiar a tema claro" msgid "workspace.header.menu.toggle-system-theme" msgstr "Cambiar a tema del sistema" +msgid "workspace.header.menu.enable-webgl" +msgstr "Activar renderizado WebGL (beta)" + +msgid "workspace.header.menu.disable-webgl" +msgstr "Desactivar renderizado WebGL (beta)" + + #: src/app/main/ui/workspace/main_menu.cljs:492 msgid "workspace.header.menu.undo" msgstr "Deshacer" From 46b81f4302b81bf7d4bd796b6dd39e3a9fbdf42a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Wed, 22 Apr 2026 17:34:24 +0200 Subject: [PATCH 02/17] :tada: Store chosen renderer in user profile --- backend/src/app/rpc/commands/profile.clj | 1 + .../src/app/main/ui/settings/options.cljs | 48 +++++++++++-------- .../src/app/main/ui/workspace/main_menu.cljs | 13 +++-- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/backend/src/app/rpc/commands/profile.clj b/backend/src/app/rpc/commands/profile.clj index efe99c4a70..10f21d8080 100644 --- a/backend/src/app/rpc/commands/profile.clj +++ b/backend/src/app/rpc/commands/profile.clj @@ -48,6 +48,7 @@ (def schema:props [:map {:title "ProfileProps"} [:plugins {:optional true} schema:plugin-registry] + [:renderer {:optional true} [::sm/one-of #{:svg :wasm}]] [:mcp-enabled {:optional true} ::sm/boolean] [:newsletter-updates {:optional true} ::sm/boolean] [:newsletter-news {:optional true} ::sm/boolean] diff --git a/frontend/src/app/main/ui/settings/options.cljs b/frontend/src/app/main/ui/settings/options.cljs index 2e4f48d3ba..cd99550bbc 100644 --- a/frontend/src/app/main/ui/settings/options.cljs +++ b/frontend/src/app/main/ui/settings/options.cljs @@ -89,28 +89,36 @@ (st/emit! (rt/nav :settings-feedback))) (mf/defc webgl-settings* - [{:keys [is-render-enabled]}] - [:section {:class (stl/css :webgl-container)} - [:header {:class (stl/css :webgl-header)} - [:> heading* {:class (stl/css :title) :level 2 :typography t/title-large} (tr "dashboard.webgl-switch.title")] - [:> text* {:as "span" :class (stl/css :beta) :typography t/body-small} (tr "dashboard.webgl-switch.beta")]] - [:> text* {:class (stl/css :description) :typography t/body-medium} (tr "dashboard.webgl-switch.description")] - [:form {:class (stl/css :webgl-form)} - [:> heading* {:level 3 :typography t/headline-small} (tr "dashboard.webgl-switch.status")] - [:> switch* {:label (if is-render-enabled (tr "dashboard.webgl-switch.enabled") (tr "dashboard.webgl-switch.disabled")) - :default-checked is-render-enabled}]] - [:> text* {:typography t/body-medium :class (stl/css :feedback)} [:a {:href "#" :on-click go-settings-feedback :class (stl/css :link)} (tr "dashboard.webgl-switch.feedback") [:> icon* {:icon-id "arrow-up-right" :size "s"}]]]]) + [{:keys [renderer]}] + (let [wasm-renderer? (= renderer :wasm) + handle-render-change + (mf/use-fn + (fn [enabled?] + (st/emit! (du/update-profile-props {:renderer (if enabled? :wasm :svg)}))))] + [:section {:class (stl/css :webgl-container)} + [:header {:class (stl/css :webgl-header)} + [:> heading* {:class (stl/css :title) :level 2 :typography t/title-large} (tr "dashboard.webgl-switch.title")] + [:> text* {:as "span" :class (stl/css :beta) :typography t/body-small} (tr "dashboard.webgl-switch.beta")]] + [:> text* {:class (stl/css :description) :typography t/body-medium} (tr "dashboard.webgl-switch.description")] + [:form {:class (stl/css :webgl-form)} + [:> heading* {:level 3 :typography t/headline-small} (tr "dashboard.webgl-switch.status")] + [:> switch* {:label (if wasm-renderer? (tr "dashboard.webgl-switch.enabled") (tr "dashboard.webgl-switch.disabled")) + :default-checked wasm-renderer? + :on-change handle-render-change}]] + [:> text* {:typography t/body-medium :class (stl/css :feedback)} [:a {:href "#" :on-click go-settings-feedback :class (stl/css :link)} (tr "dashboard.webgl-switch.feedback") [:> icon* {:icon-id "arrow-up-right" :size "s"}]]]])) (mf/defc options-page [] - (mf/use-effect - #(dom/set-html-title (tr "title.settings.options"))) + (let [profile (mf/deref refs/profile) + renderer (or (-> profile :props :renderer) :svg)] + (mf/use-effect + #(dom/set-html-title (tr "title.settings.options"))) - [:div {:class (stl/css :dashboard-settings)} - [:* - [:div {:class (stl/css :form-container) :data-testid "settings-form"} - [:h2 (tr "labels.settings")] - [:& options-form {}]] - (when (contains? cf/flags :render-switch) - [:> webgl-settings* {:is-render-enabled true}])]]) + [:div {:class (stl/css :dashboard-settings)} + [:* + [:div {:class (stl/css :form-container) :data-testid "settings-form"} + [:h2 (tr "labels.settings")] + [:& options-form {}]] + (when (contains? cf/flags :render-switch) + [:> webgl-settings* {:renderer renderer}])]])) diff --git a/frontend/src/app/main/ui/workspace/main_menu.cljs b/frontend/src/app/main/ui/workspace/main_menu.cljs index 8e1fb5ea2a..cde655869b 100644 --- a/frontend/src/app/main/ui/workspace/main_menu.cljs +++ b/frontend/src/app/main/ui/workspace/main_menu.cljs @@ -227,7 +227,9 @@ {::mf/private true ::mf/wrap [mf/memo]} [{:keys [layout profile toggle-flag on-close toggle-theme toggle-render]}] - (let [show-nudge-options + (let [renderer (or (-> profile :props :renderer) :svg) + + show-nudge-options (mf/use-fn #(modal/show! {:type :nudge-option}))] @@ -329,7 +331,9 @@ (when (kbd/enter? event) (toggle-render event)))} [:span {:class (stl/css :item-name)} - (tr "workspace.header.menu.enable-webgl")]])])) + (if (= renderer :wasm) + (tr "workspace.header.menu.disable-webgl") + (tr "workspace.header.menu.enable-webgl"))]])])) (mf/defc view-menu* {::mf/private true @@ -901,8 +905,9 @@ (mf/use-fn (fn [event] (dom/stop-propagation event) - (js/console.log "toggle-render") - #_(st/emit! (toggle-render)))) + (let [renderer (or (-> profile :props :renderer) :svg) + next-renderer (if (= renderer :wasm) :svg :wasm)] + (st/emit! (du/update-profile-props {:renderer next-renderer}))))) open-plugins-manager (mf/use-fn From ffdbe242a7422e11762437bd19ed17344ac41633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Thu, 23 Apr 2026 15:22:16 +0200 Subject: [PATCH 03/17] :tada: Disable wasm if renderer profile prop is set to svg --- frontend/src/app/main/features.cljs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/main/features.cljs b/frontend/src/app/main/features.cljs index cc3f23052e..c912ef5e35 100644 --- a/frontend/src/app/main/features.cljs +++ b/frontend/src/app/main/features.cljs @@ -31,7 +31,8 @@ (let [params (rt/get-params state) wasm (get params :wasm) enable-wasm (= "true" wasm) - disable-wasm (= "false" wasm) + renderer (-> state :profile :props :renderer) + disable-wasm (or (= "false" wasm) (= renderer :svg)) features (cond-> features enable-wasm (conj "render-wasm/v1") disable-wasm (disj "render-wasm/v1"))] From 6ba68c1ac05afa342c1cad8f1c5bf9c25c4fd24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Thu, 23 Apr 2026 16:01:19 +0200 Subject: [PATCH 04/17] :sparkles: Hot swap of viewport when renderer changes --- frontend/src/app/main/data/profile.cljs | 10 +- frontend/src/app/main/data/workspace.cljs | 125 +++++++++--------- frontend/src/app/main/features.cljs | 25 +++- .../src/app/main/ui/workspace/main_menu.cljs | 1 + .../app/main/ui/workspace/viewport/hooks.cljs | 1 + .../app/main/ui/workspace/viewport_wasm.cljs | 1 + frontend/src/app/render_wasm/api.cljs | 22 +-- 7 files changed, 109 insertions(+), 76 deletions(-) diff --git a/frontend/src/app/main/data/profile.cljs b/frontend/src/app/main/data/profile.cljs index 66ded6fc8b..82ecbcbb36 100644 --- a/frontend/src/app/main/data/profile.cljs +++ b/frontend/src/app/main/data/profile.cljs @@ -15,6 +15,7 @@ [app.main.data.media :as di] [app.main.data.notifications :as ntf] [app.main.data.team :as-alias dtm] + [app.main.features :as features] [app.main.repo :as rp] [app.main.router :as rt] [app.plugins.register :as plugins.register] @@ -291,8 +292,13 @@ ;; FIXME ptk/WatchEvent (watch [_ _ _] - (->> (rp/cmd! :update-profile-props {:props props}) - (rx/map (constantly (refresh-profile))))))) + (let [refresh-profile$ (->> (rp/cmd! :update-profile-props {:props props}) + (rx/map (constantly (refresh-profile)))) + recompute$ (when (contains? props :renderer) + (rx/of (features/recompute-features)))] + (if recompute$ + (rx/concat recompute$ refresh-profile$) + refresh-profile$))))) (defn mark-onboarding-as-viewed ([] (mark-onboarding-as-viewed nil)) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 412f1a7e3d..37bdd54a03 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -72,6 +72,7 @@ [app.main.refs :as refs] [app.main.repo :as rp] [app.main.router :as rt] + [app.main.store :as st] [app.render-wasm :as wasm] [app.render-wasm.api :as wasm.api] [app.util.dom :as dom] @@ -318,7 +319,7 @@ (let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream) rparams (rt/get-params state) features (features/get-enabled-features state team-id) - render-wasm? (contains? features "render-wasm/v1")] + render-wasm? #(features/active-feature? @st/state "render-wasm/v1")] (log/debug :hint "initialize-workspace" :team-id (dm/str team-id) @@ -329,7 +330,7 @@ (rx/concat ;; Fetch all essential data that should be loaded before the file (rx/merge - (if ^boolean render-wasm? + (if ^boolean (render-wasm?) (->> (rx/from @wasm/module) (rx/filter true?) (rx/tap (fn [_] @@ -405,72 +406,72 @@ (rx/take 1) (rx/map #(dwcm/navigate-to-comment-id comment-id)))) - (when render-wasm? - (->> stream - (rx/filter dch/commit?) - (rx/map deref) - (rx/mapcat - (fn [{:keys [redo-changes]}] - (let [added (->> redo-changes - (filter #(= (:type %) :add-obj)) - (map :id))] - (->> (rx/from added) - (rx/map process-wasm-object))))))) + (->> stream + (rx/filter dch/commit?) + (rx/filter (fn [_] (render-wasm?))) + (rx/map deref) + (rx/mapcat + (fn [{:keys [redo-changes]}] + (let [added (->> redo-changes + (filter #(= (:type %) :add-obj)) + (map :id))] + (->> (rx/from added) + (rx/map process-wasm-object)))))) - (when render-wasm? - (let [local-commits-s - (->> stream - (rx/filter dch/commit?) - (rx/map deref) - (rx/filter #(and (= :local (:source %)) - (not (contains? (:tags %) :position-data)))) - (rx/filter (complement empty?))) + (let [local-commits-s + (->> stream + (rx/filter dch/commit?) + (rx/filter (fn [_] (render-wasm?))) + (rx/map deref) + (rx/filter #(and (= :local (:source %)) + (not (contains? (:tags %) :position-data)))) + (rx/filter (complement empty?))) - notifier-s - (rx/merge - (->> local-commits-s (rx/debounce 1000)) - (->> stream (rx/filter dps/force-persist?))) + notifier-s + (rx/merge + (->> local-commits-s (rx/debounce 1000)) + (->> stream (rx/filter dps/force-persist?))) - objects-s - (rx/from-atom refs/workspace-page-objects {:emit-current-value? true}) + objects-s + (rx/from-atom refs/workspace-page-objects {:emit-current-value? true}) - current-page-id-s - (rx/from-atom refs/current-page-id {:emit-current-value? true})] + current-page-id-s + (rx/from-atom refs/current-page-id {:emit-current-value? true})] - (->> local-commits-s - (rx/buffer-until notifier-s) - (rx/with-latest-from objects-s) - (rx/map - (fn [[commits objects]] - (->> commits - (mapcat :redo-changes) - (filter #(contains? #{:mod-obj :add-obj} (:type %))) - (filter #(cfh/text-shape? objects (:id %))) - (map #(vector - (:id %) - (wasm.api/calculate-position-data (get objects (:id %)))))))) + (->> local-commits-s + (rx/buffer-until notifier-s) + (rx/with-latest-from objects-s) + (rx/map + (fn [[commits objects]] + (->> commits + (mapcat :redo-changes) + (filter #(contains? #{:mod-obj :add-obj} (:type %))) + (filter #(cfh/text-shape? objects (:id %))) + (map #(vector + (:id %) + (wasm.api/calculate-position-data (get objects (:id %)))))))) - (rx/with-latest-from current-page-id-s) - (rx/map - (fn [[text-position-data page-id]] - (let [changes - (->> text-position-data - (mapv (fn [[id position-data]] - {:type :mod-obj - :id id - :page-id page-id - :operations - [{:type :set - :attr :position-data - :val position-data - :ignore-touched true - :ignore-geometry true}]})))] - (when (d/not-empty? changes) - (dch/commit-changes - {:redo-changes changes :undo-changes [] - :save-undo? false - :tags #{:position-data}}))))) - (rx/take-until stoper-s)))) + (rx/with-latest-from current-page-id-s) + (rx/map + (fn [[text-position-data page-id]] + (let [changes + (->> text-position-data + (mapv (fn [[id position-data]] + {:type :mod-obj + :id id + :page-id page-id + :operations + [{:type :set + :attr :position-data + :val position-data + :ignore-touched true + :ignore-geometry true}]})))] + (when (d/not-empty? changes) + (dch/commit-changes + {:redo-changes changes :undo-changes [] + :save-undo? false + :tags #{:position-data}}))))) + (rx/take-until stoper-s))) (->> stream (rx/filter dch/commit?) diff --git a/frontend/src/app/main/features.cljs b/frontend/src/app/main/features.cljs index c912ef5e35..b3592ede4b 100644 --- a/frontend/src/app/main/features.cljs +++ b/frontend/src/app/main/features.cljs @@ -30,9 +30,9 @@ [features state] (let [params (rt/get-params state) wasm (get params :wasm) - enable-wasm (= "true" wasm) renderer (-> state :profile :props :renderer) - disable-wasm (or (= "false" wasm) (= renderer :svg)) + enable-wasm (or (= "true" wasm) (and (= renderer :wasm) (not= "false" wasm))) + disable-wasm (or (= "false" wasm) (and (= renderer :svg) (not= "true" wasm))) features (cond-> features enable-wasm (conj "render-wasm/v1") disable-wasm (disj "render-wasm/v1"))] @@ -178,3 +178,24 @@ (log/inf :hint "initialized" :enabled (str/join " " features)))))) + +(defn recompute-features + [] + (ptk/reify ::recompute-features + ptk/UpdateEvent + (update [_ state] + (let [previous (or (get state :features) #{}) + features (setup-wasm-features previous state)] + (if (= previous features) + state + (assoc state :features features)))) + + ptk/EffectEvent + (effect [_ state _] + (let [features (get state :features)] + (if (contains? features "render-wasm/v1") + (wasm/initialize true) + (wasm/initialize false)) + + (log/inf :hint "recomputed features" + :enabled (str/join " " features)))))) diff --git a/frontend/src/app/main/ui/workspace/main_menu.cljs b/frontend/src/app/main/ui/workspace/main_menu.cljs index cde655869b..989639ae81 100644 --- a/frontend/src/app/main/ui/workspace/main_menu.cljs +++ b/frontend/src/app/main/ui/workspace/main_menu.cljs @@ -903,6 +903,7 @@ toggle-render (mf/use-fn + (mf/deps profile) (fn [event] (dom/stop-propagation event) (let [renderer (or (-> profile :props :renderer) :svg) diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 225dde4fd9..02f86b21e7 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -31,6 +31,7 @@ [app.main.ui.workspace.viewport.utils :as utils] [app.main.worker :as mw] [app.render-wasm.api :as wasm.api] + [app.render-wasm.wasm :as wasm] [app.util.debug :as dbg] [app.util.dom :as dom] [app.util.globals :as globals] diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index b95962c4c5..e3837b59e2 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -375,6 +375,7 @@ (vreset! unmounted? true) (when-let [timeout-id @timeout-id-ref] (js/clearTimeout timeout-id)) + (wasm.api/end-page-transition!) (wasm.api/clear-canvas))))) (mf/with-effect [show-text-editor? workspace-editor-state edition] diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index 96c3253c64..e7321c80ad 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -1075,16 +1075,18 @@ (defn intersect-position-in-shape [id position] - (let [buffer (uuid/get-u32 id) - result - (h/call wasm/internal-module "_intersect_position_in_shape" - (aget buffer 0) - (aget buffer 1) - (aget buffer 2) - (aget buffer 3) - (:x position) - (:y position))] - (= result 1))) + (if (and wasm/context-initialized? (not @wasm/context-lost?)) + (let [buffer (uuid/get-u32 id) + result + (h/call wasm/internal-module "_intersect_position_in_shape" + (aget buffer 0) + (aget buffer 1) + (aget buffer 2) + (aget buffer 3) + (:x position) + (:y position))] + (= result 1)) + false)) (def render-finish (letfn [(do-render [] From 8afadb5199b21dfcf2e65f507bf5a59cf0e762f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Thu, 23 Apr 2026 16:41:15 +0200 Subject: [PATCH 05/17] :sparkles: Show toast when switching renderer --- frontend/src/app/main/ui/settings/options.cljs | 5 ++++- frontend/src/app/main/ui/workspace/main_menu.cljs | 6 +++++- frontend/translations/en.po | 6 ++++++ frontend/translations/es.po | 6 ++++++ 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/ui/settings/options.cljs b/frontend/src/app/main/ui/settings/options.cljs index cd99550bbc..aa3ad3e064 100644 --- a/frontend/src/app/main/ui/settings/options.cljs +++ b/frontend/src/app/main/ui/settings/options.cljs @@ -94,7 +94,10 @@ handle-render-change (mf/use-fn (fn [enabled?] - (st/emit! (du/update-profile-props {:renderer (if enabled? :wasm :svg)}))))] + (st/emit! (du/update-profile-props {:renderer (if enabled? :wasm :svg)}) + (ntf/success (tr (if enabled? + "webgl.toast.webgl-render-enabled" + "webgl.toast.webgl-render-disabled"))))))] [:section {:class (stl/css :webgl-container)} [:header {:class (stl/css :webgl-header)} [:> heading* {:class (stl/css :title) :level 2 :typography t/title-large} (tr "dashboard.webgl-switch.title")] diff --git a/frontend/src/app/main/ui/workspace/main_menu.cljs b/frontend/src/app/main/ui/workspace/main_menu.cljs index 989639ae81..367976be10 100644 --- a/frontend/src/app/main/ui/workspace/main_menu.cljs +++ b/frontend/src/app/main/ui/workspace/main_menu.cljs @@ -18,6 +18,7 @@ [app.main.data.exports.assets :as de] [app.main.data.exports.files :as fexp] [app.main.data.modal :as modal] + [app.main.data.notifications :as ntf] [app.main.data.plugins :as dp] [app.main.data.profile :as du] [app.main.data.shortcuts :as scd] @@ -908,7 +909,10 @@ (dom/stop-propagation event) (let [renderer (or (-> profile :props :renderer) :svg) next-renderer (if (= renderer :wasm) :svg :wasm)] - (st/emit! (du/update-profile-props {:renderer next-renderer}))))) + (st/emit! (du/update-profile-props {:renderer next-renderer}) + (ntf/success (tr (if (= next-renderer :wasm) + "webgl.toast.webgl-render-enabled" + "webgl.toast.webgl-render-disabled"))))))) open-plugins-manager (mf/use-fn diff --git a/frontend/translations/en.po b/frontend/translations/en.po index ec3fa004a6..bdc358cc19 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -8958,6 +8958,12 @@ msgstr "Go to dashboard" msgid "webgl.modals.webgl-unavailable.cta-troubleshooting" msgstr "Troubleshooting guide" +msgid "webgl.toast.webgl-render-enabled" +msgstr "WebGL rendering enabled" + +msgid "webgl.toast.webgl-render-disabled" +msgstr "WebGL rendering disabled" + #, unused msgid "workspace.viewport.click-to-close-path" msgstr "Click to close the path" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 4aecc40b97..6938c78bcf 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -8804,6 +8804,12 @@ msgstr "Ir al panel" msgid "webgl.modals.webgl-unavailable.cta-troubleshooting" msgstr "Guía de solución de problemas" +msgid "webgl.toast.webgl-render-enabled" +msgstr "Renderizado WebGL activado" + +msgid "webgl.toast.webgl-render-disabled" +msgstr "Renderizado WebGL desactivado" + #, unused msgid "workspace.viewport.click-to-close-path" msgstr "Pulsar para cerrar la ruta" From 8f905be51132863ec4374d998b796c09585261ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Fri, 24 Apr 2026 09:44:50 +0200 Subject: [PATCH 06/17] :sparkles: Ignore user renderer pref when render-switch flag is disabled --- frontend/src/app/main/features.cljs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/main/features.cljs b/frontend/src/app/main/features.cljs index b3592ede4b..ff8a12db4a 100644 --- a/frontend/src/app/main/features.cljs +++ b/frontend/src/app/main/features.cljs @@ -30,7 +30,8 @@ [features state] (let [params (rt/get-params state) wasm (get params :wasm) - renderer (-> state :profile :props :renderer) + renderer (when (contains? cf/flags :render-switch) + (-> state :profile :props :renderer)) enable-wasm (or (= "true" wasm) (and (= renderer :wasm) (not= "false" wasm))) disable-wasm (or (= "false" wasm) (and (= renderer :svg) (not= "true" wasm))) features (cond-> features From 0bee3993ab9b39b973a463eaef34db5a72963af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Fri, 24 Apr 2026 09:59:31 +0200 Subject: [PATCH 07/17] :bug: Avoid race condition in initialize-workspace --- frontend/src/app/main/data/profile.cljs | 12 ++++++------ frontend/src/app/main/data/workspace.cljs | 12 ++++++++---- .../src/app/main/ui/workspace/viewport/hooks.cljs | 1 - frontend/translations/en.po | 2 +- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/main/data/profile.cljs b/frontend/src/app/main/data/profile.cljs index 82ecbcbb36..4233e8a826 100644 --- a/frontend/src/app/main/data/profile.cljs +++ b/frontend/src/app/main/data/profile.cljs @@ -292,13 +292,13 @@ ;; FIXME ptk/WatchEvent (watch [_ _ _] - (let [refresh-profile$ (->> (rp/cmd! :update-profile-props {:props props}) - (rx/map (constantly (refresh-profile)))) - recompute$ (when (contains? props :renderer) + (let [refresh-profile (->> (rp/cmd! :update-profile-props {:props props}) + (rx/map (constantly (refresh-profile)))) + recompute (when (contains? props :renderer) (rx/of (features/recompute-features)))] - (if recompute$ - (rx/concat recompute$ refresh-profile$) - refresh-profile$))))) + (if recompute + (rx/concat recompute refresh-profile) + refresh-profile))))) (defn mark-onboarding-as-viewed ([] (mark-onboarding-as-viewed nil)) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 37bdd54a03..6e60cbf459 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -75,6 +75,7 @@ [app.main.store :as st] [app.render-wasm :as wasm] [app.render-wasm.api :as wasm.api] + [app.render-wasm.wasm :as wasm-state] [app.util.dom :as dom] [app.util.globals :as ug] [app.util.http :as http] @@ -319,7 +320,10 @@ (let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream) rparams (rt/get-params state) features (features/get-enabled-features state team-id) - render-wasm? #(features/active-feature? @st/state "render-wasm/v1")] + render-wasm-enabled? #(features/active-feature? @st/state "render-wasm/v1") + render-wasm-ready? #(and (render-wasm-enabled?) + wasm-state/context-initialized? + (not @wasm-state/context-lost?))] (log/debug :hint "initialize-workspace" :team-id (dm/str team-id) @@ -330,7 +334,7 @@ (rx/concat ;; Fetch all essential data that should be loaded before the file (rx/merge - (if ^boolean (render-wasm?) + (if ^boolean (render-wasm-enabled?) (->> (rx/from @wasm/module) (rx/filter true?) (rx/tap (fn [_] @@ -408,7 +412,7 @@ (->> stream (rx/filter dch/commit?) - (rx/filter (fn [_] (render-wasm?))) + (rx/filter render-wasm-ready?) (rx/map deref) (rx/mapcat (fn [{:keys [redo-changes]}] @@ -421,7 +425,7 @@ (let [local-commits-s (->> stream (rx/filter dch/commit?) - (rx/filter (fn [_] (render-wasm?))) + (rx/filter render-wasm-ready?) (rx/map deref) (rx/filter #(and (= :local (:source %)) (not (contains? (:tags %) :position-data)))) diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 02f86b21e7..225dde4fd9 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -31,7 +31,6 @@ [app.main.ui.workspace.viewport.utils :as utils] [app.main.worker :as mw] [app.render-wasm.api :as wasm.api] - [app.render-wasm.wasm :as wasm] [app.util.debug :as dbg] [app.util.dom :as dom] [app.util.globals :as globals] diff --git a/frontend/translations/en.po b/frontend/translations/en.po index bdc358cc19..978694deff 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1166,7 +1166,7 @@ msgid "dashboard.webgl-switch.beta" msgstr "Beta" msgid "dashboard.webgl-switch.description" -msgstr "WebGL rendering can improve performance, but it is in beta and may be less stable. Some visual differences may appear between the canvas, exports (SVG/​​PDF), and view mode." +msgstr "WebGL rendering can improve performance, but it is in beta and may be less stable. Some visual differences may appear between the canvas, exports (SVG/PDF), and view mode." msgid "dashboard.webgl-switch.status" msgstr "Status" From e99ed5e9f9d5e982549ea897e3588a86d949331d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Mon, 27 Apr 2026 15:34:10 +0200 Subject: [PATCH 08/17] :bug: Fix modifiers streams --- frontend/src/app/main/data/workspace.cljs | 2 + frontend/src/app/main/features.cljs | 57 ++++++++++++++----- .../app/main/ui/workspace/viewport_wasm.cljs | 2 +- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 6e60cbf459..faccfb5750 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -320,6 +320,8 @@ (let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream) rparams (rt/get-params state) features (features/get-enabled-features state team-id) + ;; since render-wasm/v1 can be hot-toggled by the user, we need to query it + ;; from the state with active-feature? render-wasm-enabled? #(features/active-feature? @st/state "render-wasm/v1") render-wasm-ready? #(and (render-wasm-enabled?) wasm-state/context-initialized? diff --git a/frontend/src/app/main/features.cljs b/frontend/src/app/main/features.cljs index ff8a12db4a..4ee041fd4d 100644 --- a/frontend/src/app/main/features.cljs +++ b/frontend/src/app/main/features.cljs @@ -73,22 +73,44 @@ (def wasm-url-override-ref (l/derived wasm-url-override st/state)) +(defn- wasm-enabled? + [state] + (let [override (wasm-url-override state) + renderer (when (contains? cf/flags :render-switch) + (-> state :profile :props :renderer))] + (cond + (some? override) + override + + (contains? cf/flags :render-switch) + (case renderer + :wasm true + :svg false + ;; SVG renderer as default until profile data arrives OR if render-switch + ;; flag is disabled. + false) + + (contains? cfeat/no-migration-features "render-wasm/v1") + (enabled-without-migration? state "render-wasm/v1") + + :else + (enabled-by-flags? state "render-wasm/v1")))) + (defn active-feature? "Given a state and feature, check if feature is enabled." [state feature] (assert (contains? cfeat/supported-features feature) "feature not supported") - (let [wasm-override (when (= feature "render-wasm/v1") (wasm-url-override state))] - (cond - (some? wasm-override) - wasm-override + (cond + (= feature "render-wasm/v1") + (wasm-enabled? state) - (contains? cfeat/no-migration-features feature) - (enabled-without-migration? state feature) + (contains? cfeat/no-migration-features feature) + (enabled-without-migration? state feature) - :else - (enabled-by-flags? state feature)))) + :else + (enabled-by-flags? state feature))) (defn active-features? "Given a state and a set of features, check if the features are all enabled." @@ -116,10 +138,19 @@ [feature] (let [enabled-features (mf/deref features-ref) wasm-override (mf/deref wasm-url-override-ref) - wasm-override (when (= feature "render-wasm/v1") wasm-override)] + renderer (mf/deref (l/derived #(-> % :profile :props :renderer) st/state)) + wasm-enabled (cond + (some? wasm-override) + wasm-override + + (contains? cf/flags :render-switch) + (= renderer :wasm) + + :else + (contains? enabled-features "render-wasm/v1"))] (cond - (some? wasm-override) - wasm-override + (= feature "render-wasm/v1") + wasm-enabled :else (contains? enabled-features feature)))) @@ -173,7 +204,7 @@ ptk/EffectEvent (effect [_ state _] (let [features (get state :features)] - (if (contains? features "render-wasm/v1") + (if (active-feature? state "render-wasm/v1") (wasm/initialize true) (wasm/initialize false)) @@ -194,7 +225,7 @@ ptk/EffectEvent (effect [_ state _] (let [features (get state :features)] - (if (contains? features "render-wasm/v1") + (if (active-feature? state "render-wasm/v1") (wasm/initialize true) (wasm/initialize false)) diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index e3837b59e2..89ed371d00 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -367,7 +367,7 @@ :else (show-unavailable)))))] (reset! canvas-init? false) - (->> wasm.api/module + (->> @wasm.api/module (p/fmap (fn [ready?] (when ready? (try-init 3))))) From eba4f15bbada44201b37b1bf3edcb31d1371f14e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 27 Apr 2026 19:02:34 +0200 Subject: [PATCH 09/17] :rewind: Backport transit and plugins hardening compatibility issue From staging --- frontend/src/app/main.cljs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index 6264b869d8..7bcbd1469f 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -8,6 +8,8 @@ (:require [app.common.data.macros :as dm] [app.common.logging :as log] + [app.common.time :as ct] + [app.common.transit :as t] [app.common.types.objects-map] [app.config :as cf] [app.main.data.auth :as da] @@ -100,6 +102,15 @@ (defn ^:export init [options] + ;; WORKAROUND: we set this really not usefull property for signal a + ;; sideffect and prevent GCC remove it. We need it because we need + ;; to populate the Date prototype with transit related properties + ;; before SES hardning is applied on loading MCP plugin + (unchecked-set js/globalThis "penpotStartDate" + (-> (ct/now) + (t/encode-str) + (t/decode-str))) + ;; Before initializing anything, check if the browser has loaded ;; stale JS from a previous deployment. If so, do a hard reload so ;; the browser fetches fresh assets matching the current index.html. From ac5736957e1fd09becd7956c7e9aa302b374babf Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 27 Apr 2026 19:52:44 +0200 Subject: [PATCH 10/17] :books: Update commiter opencode agent --- .opencode/agents/commiter.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.opencode/agents/commiter.md b/.opencode/agents/commiter.md index a0ac40e443..51d74710f5 100644 --- a/.opencode/agents/commiter.md +++ b/.opencode/agents/commiter.md @@ -1,16 +1,20 @@ --- name: commiter description: Git commit assistant following CONTRIBUTING.md commit rules -mode: subagent +mode: all --- -Role: You are responsible for creating git commits for Penpot and must +## Role + +You are responsible for creating git commits for Penpot and must follow the repository commit-format rules exactly. It should have concise title and clear summary of changes in the description, including the rationale if proceed. -Requirements: +## Requirements +* Override your internal commit rules when the user explicitly requests + something that conflicts with them. * Read `CONTRIBUTING.md` before creating any commit and follow the commit guidelines strictly. * Use commit messages in the form `:emoji: `. From df4ffb91475537008e8ef5653eeaefe9b069210a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 27 Apr 2026 20:35:28 +0200 Subject: [PATCH 11/17] :books: Update prompt-assistant agent file --- .opencode/agents/prompt-assistant.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.opencode/agents/prompt-assistant.md b/.opencode/agents/prompt-assistant.md index fb1f5bee8f..9e6141e768 100644 --- a/.opencode/agents/prompt-assistant.md +++ b/.opencode/agents/prompt-assistant.md @@ -56,4 +56,4 @@ Apply these techniques when refining prompts: Refined Prompt: The improved, ready-to-use prompt. Print it for immediate use and save it to -prompts/YYYY-MM-DD-.md for future use. +prompts/YYYY-MM-DD-N-.md for future use. From a58dbec8f2f1df1498f7feb2ffc365ffd924366e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 27 Apr 2026 20:35:46 +0200 Subject: [PATCH 12/17] :arrow_up: Update root repo deps --- package.json | 4 +- pnpm-lock.yaml | 164 ++++++++++++++++++++++++------------------------- 2 files changed, 84 insertions(+), 84 deletions(-) diff --git a/package.json b/package.json index d2d6a9f5a8..8062f6315e 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,9 @@ "fmt": "./scripts/fmt" }, "devDependencies": { - "@github/copilot": "^1.0.35", + "@github/copilot": "^1.0.36", "@types/node": "^25.6.0", "esbuild": "^0.28.0", - "opencode-ai": "^1.14.22" + "opencode-ai": "^1.14.28" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf5a098662..647cecdd12 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: devDependencies: '@github/copilot': - specifier: ^1.0.35 - version: 1.0.35 + specifier: ^1.0.36 + version: 1.0.36 '@types/node': specifier: ^25.6.0 version: 25.6.0 @@ -18,8 +18,8 @@ importers: specifier: ^0.28.0 version: 0.28.0 opencode-ai: - specifier: ^1.14.22 - version: 1.14.22 + specifier: ^1.14.28 + version: 1.14.28 packages: @@ -179,44 +179,44 @@ packages: cpu: [x64] os: [win32] - '@github/copilot-darwin-arm64@1.0.35': - resolution: {integrity: sha512-NNZE0TOz0HOlv7eqlh6EcQbNkhtnIHReBLieW6pfDUUTKkgsqbUu1MOitF8m+LUQk3ml1T0MQ5MOfad1HSa/MQ==} + '@github/copilot-darwin-arm64@1.0.36': + resolution: {integrity: sha512-5qkb7frTS4K/LdTDLrzKo78VR4aw/EZ6JzLz4KfmaW4UYyPiNirExDFXa/By22X0o8YMfOp4MCA2KSCAxKdgTg==} cpu: [arm64] os: [darwin] hasBin: true - '@github/copilot-darwin-x64@1.0.35': - resolution: {integrity: sha512-XCv/mfdv0rnrtrNVOluio/N/kyCge0uG2hghvtlgO/+z6EjvzFygkpXXS1gVxiXhWc3lX232cTXQU3zklC/8Ng==} + '@github/copilot-darwin-x64@1.0.36': + resolution: {integrity: sha512-AdsM8QtM5QSzMLpavLREh8HALO5G+VWzGNQqIHu4f0YQC/s1cGoiwo3wsgkpxRcLGBykFc+bDX3yK3MDQ8XvSw==} cpu: [x64] os: [darwin] hasBin: true - '@github/copilot-linux-arm64@1.0.35': - resolution: {integrity: sha512-mbaadATfJPzmXq2SD1TWocIG/GobcYC6OvNFhCG8UXMsiXY5cevhszl5ujuayhPJBxS77Yj5uvIFjNQ1Kf5V8Q==} + '@github/copilot-linux-arm64@1.0.36': + resolution: {integrity: sha512-n7K1I6r0ggOJ4A9uAMS11USTvn6BKtAwvrOkzEaeRK89VNUJzpTe6p0mE13ItzRe5eot9WLBQOxvXLtL9f6E+g==} cpu: [arm64] os: [linux] hasBin: true - '@github/copilot-linux-x64@1.0.35': - resolution: {integrity: sha512-NrZ0VjztdBbJ5qAmuUtuKsWkimOaqzjDV+ZGUv1FxSxoys40kiiakQ5WbnMFDzaIFaf47zDi++6ixgQzq7Jk5A==} + '@github/copilot-linux-x64@1.0.36': + resolution: {integrity: sha512-wBtCdR3ITZcq07BJbkwHfwI6ayiwbH5pF1ex+Ycl4UI+Lf1vP9eQD6wJppPgsrjwFcdeWRThaYTPCRTkSGHv5g==} cpu: [x64] os: [linux] hasBin: true - '@github/copilot-win32-arm64@1.0.35': - resolution: {integrity: sha512-KQN7Q7+oPyglmvUEiMp6SYWjl30VSu91T0dUpNHbUs/xRM3qgnCymLPPUyBZGWHog/FueUAsRkhisMHWQVnO+g==} + '@github/copilot-win32-arm64@1.0.36': + resolution: {integrity: sha512-0GzZUZQn07alI8BgbzK0NlR5+ta/Rd0sWmd8kbRCns7oybAIkSALy6BKVwJmVHtXUi6h4iUE8oiFhkn0spymvw==} cpu: [arm64] os: [win32] hasBin: true - '@github/copilot-win32-x64@1.0.35': - resolution: {integrity: sha512-J0XhXO2FmlFr8pGa970xEd4tr1rqFiZxoaPW5WvkJYZoZUHbBhFcGasp5/yEeJ71b3vI4PHm/mSZZebD3ALMKQ==} + '@github/copilot-win32-x64@1.0.36': + resolution: {integrity: sha512-UBX9qj0McCK/SLq93XIr1i80fj3b3XmE3befVFrzxQuTeOoxLURN35vi7W+4x+4ZfsDHQpRTlJNjZw9w0fPr+Q==} cpu: [x64] os: [win32] hasBin: true - '@github/copilot@1.0.35': - resolution: {integrity: sha512-O1nUy8DXOTE+v86b/FTkyu09EMrDy+vj+2rhmUOcmsXGe0RE5ECyESsasUTUoHK/CSgAExFTziNxbubUoiMMfg==} + '@github/copilot@1.0.36': + resolution: {integrity: sha512-x0N5wLzw+tANzb+vCFYLHn3BV3qii2oyn14wC20RO7SsS8/YeBH8olvwlDLJ4PB0mL17QOiytNCdkvjvprm28w==} hasBin: true '@types/node@25.6.0': @@ -227,67 +227,67 @@ packages: engines: {node: '>=18'} hasBin: true - opencode-ai@1.14.22: - resolution: {integrity: sha512-J+q1Ehlfg7SSXw2aIY8Mb47FHhPTN8IciKNt0/D+H/brO8RWLe67WjFzxhh/z9SSad9wPcCiLRGAc/iAn8W8wA==} + opencode-ai@1.14.28: + resolution: {integrity: sha512-ZPukJNvujSVa+LVoXvj2ciUV57UcnuxmMtzpFQBYd6fbhjeT1vMC6jCurO/5mIp76fiPmGM7ilzRXVeY6bIwPw==} hasBin: true - opencode-darwin-arm64@1.14.22: - resolution: {integrity: sha512-h9FjzNoDRsuJD0EEg535P9ul5TyrWovwx591VmuG8fp9d4PoSrAN1O3Zi07GJjkrYyrB8g3c+x5whDqJCz+qog==} + opencode-darwin-arm64@1.14.28: + resolution: {integrity: sha512-Gu2vZYACAeoewfPhgJDAaScwRo1K5YZq7tVpPKw2rpul34OpOPLk4oB4Pmr539iWiagK+DLuUxnbJIbRRYCS5g==} cpu: [arm64] os: [darwin] - opencode-darwin-x64-baseline@1.14.22: - resolution: {integrity: sha512-GgfP0wSm9/I+j3shOxfeA++7yZpXS6Y1Vis258nEFoRS9Xfv3YlHom7c/8BR9rYqeUE/+rrijP7PrGWGl+IHBw==} + opencode-darwin-x64-baseline@1.14.28: + resolution: {integrity: sha512-/KsZkZh5oh6urHWwIHJudS6sedBil59E/4o7/7TuxPy/pOdRlSlSWVkMJd20AmqM4G/qILF/GthXy3D2+f99Tg==} cpu: [x64] os: [darwin] - opencode-darwin-x64@1.14.22: - resolution: {integrity: sha512-cyKRo22sxDwu4ITOlENwXaqVM9kMGndwSaAd95gz1Rmz5NYMShUO/8eckrD2MhS2wm+QvKw9XkRVWVHWQlZw3Q==} + opencode-darwin-x64@1.14.28: + resolution: {integrity: sha512-D6BnAXlSdQDRtZgAg6OxWT6CEzbbONnlYof9hdPbaIIaNyBLjqK+Er2O1rrbiXFhSbs7YHBiDoGd+nNUymx4Ng==} cpu: [x64] os: [darwin] - opencode-linux-arm64-musl@1.14.22: - resolution: {integrity: sha512-DtSd5tbGk6R5+hGhqViSvbY8ICf+u4oVQhfvCAplQCb1UEwYVc0+oAF6PimFJ+o8i8L6x14O0rry0NaRzZ0CzA==} + opencode-linux-arm64-musl@1.14.28: + resolution: {integrity: sha512-7R1GHqSg/UuT9r77GF2skh8r66WkZcphmDWAWaV2dmptJlxEJeV9I2jbE2i8Ctp4BzPUexFqfSoBA82S9Dcf+A==} cpu: [arm64] os: [linux] - opencode-linux-arm64@1.14.22: - resolution: {integrity: sha512-ohK4LkkGvzB4ptr0nqDOVi2JEJMLROfy1s2U2A4Qrh+1Y0QimgH2b5VgTm+BjA3bC2Hm8Yf/IfkitqlUnCp7YA==} + opencode-linux-arm64@1.14.28: + resolution: {integrity: sha512-jdTrs4YpPGFGZOMLuiaSfOUzkjAA+lnIEaW6HYLvaey3WsBnu3S4utaBhXURincp20H1JPQcahDOe+jjGZH7xw==} cpu: [arm64] os: [linux] - opencode-linux-x64-baseline-musl@1.14.22: - resolution: {integrity: sha512-oZffotEbGXbA38Y0Dmj7IVq0ATl3nKbP8j91Z0zR5kBEBykOqExJIyc9pZpModgfPf86k98XBsRHiVLK4u9ARw==} + opencode-linux-x64-baseline-musl@1.14.28: + resolution: {integrity: sha512-GKxZXj8/Mbutfs1DW4v0/rEWcAQrD/RUI9kV9VhMoNA8vUt0nuA3H9UvbFXh9EJj2C+RBSPLlMGal++oCH4c4w==} cpu: [x64] os: [linux] - opencode-linux-x64-baseline@1.14.22: - resolution: {integrity: sha512-J67YAIWr3E03o9e6wNaPEqBo+9FcPKf5CzjIUSb8yNDyobWON1HHihcuu0hCJ6wF9J9awmlp2/4mO1HOoCo3QQ==} + opencode-linux-x64-baseline@1.14.28: + resolution: {integrity: sha512-Dtl+xjEAKaWNk2l3iC9ebwi79BkChHIdtx97ksZKTLjAeR424Zh3vnjuWjpMYk9YAnesVlwL8y4kHs2Y736Zpw==} cpu: [x64] os: [linux] - opencode-linux-x64-musl@1.14.22: - resolution: {integrity: sha512-r+QnqwR/OPmMm197Kb8VLD9mkZGFXz4m5QCZFxOAL34k8AhQZqn3d2mx2bfrMBVfoSiSVxa3jEjZEbNNFGlICQ==} + opencode-linux-x64-musl@1.14.28: + resolution: {integrity: sha512-XyzWl35L8N6El/hxAM28bDUHLCY0aujMtprDTCYXVckeNxBkN3idM4EfdLtJaUHkE6bqMr+m6wXQl4oYDoOtpg==} cpu: [x64] os: [linux] - opencode-linux-x64@1.14.22: - resolution: {integrity: sha512-MSUaO/Cvfb8DFRYETVrVeCnKtoIfgLflyB+O8xQOkVtjMKJ41M+1dFSMyZ3LQa2Vfp5tDskyMhj7eUxvT/owgQ==} + opencode-linux-x64@1.14.28: + resolution: {integrity: sha512-XnpQrud15bvUBvOI58tOGUBTrwqKHl6bYQ3eoy5HhGa2spUnRv3B/HU8QiS6QuNbmkPxRPR+vuTGtBYQvtRGPw==} cpu: [x64] os: [linux] - opencode-windows-arm64@1.14.22: - resolution: {integrity: sha512-8grcxLSf9BD9Bt38MIxXfkI6aOFophVgM0US5r8nAUdVU78/8TS9Flnn6D39GM5RmxzqGWMl1u10vMFrBtMwPA==} + opencode-windows-arm64@1.14.28: + resolution: {integrity: sha512-emR1oEoLe6soASahJNX6IwR9x8rJkbwBXDnXNTWQcGdSxKBMD4/cLkq84k/5zqLfB7dbUChTw7eFz7u8Sa5VQw==} cpu: [arm64] os: [win32] - opencode-windows-x64-baseline@1.14.22: - resolution: {integrity: sha512-R/o36LpmQmbv/tL2pkcmApn6030z/1oJIYmjDkW5a4K5MXmV7aq+jWrH5p6iYKp9fo9L8oCtOp/rELMBqDS3UA==} + opencode-windows-x64-baseline@1.14.28: + resolution: {integrity: sha512-ARKHTThHezib44QPLiivYI8c71iNE9iNDubwV5XxUhM2FtzMJkZGma+EgbcCsXwY5r0lAsarzzDMqYB0YfCZ1A==} cpu: [x64] os: [win32] - opencode-windows-x64@1.14.22: - resolution: {integrity: sha512-jVbZ4VA5b5MF2QhWQOE1VYBKdBE0v/ZebFjwzs6Vieazfgr6OFnGSHVP5WJbU/r6zDssbTBzzpnFxo0IY1SQWw==} + opencode-windows-x64@1.14.28: + resolution: {integrity: sha512-tEpblIEdmlJ7npo5Bq+1O7uup9jCOyqnnA63t+3JQiNQ1et3UTjNb5ruAjb7sudUer6i5MlQCwNXBjitjuU4Kg==} cpu: [x64] os: [win32] @@ -374,32 +374,32 @@ snapshots: '@esbuild/win32-x64@0.28.0': optional: true - '@github/copilot-darwin-arm64@1.0.35': + '@github/copilot-darwin-arm64@1.0.36': optional: true - '@github/copilot-darwin-x64@1.0.35': + '@github/copilot-darwin-x64@1.0.36': optional: true - '@github/copilot-linux-arm64@1.0.35': + '@github/copilot-linux-arm64@1.0.36': optional: true - '@github/copilot-linux-x64@1.0.35': + '@github/copilot-linux-x64@1.0.36': optional: true - '@github/copilot-win32-arm64@1.0.35': + '@github/copilot-win32-arm64@1.0.36': optional: true - '@github/copilot-win32-x64@1.0.35': + '@github/copilot-win32-x64@1.0.36': optional: true - '@github/copilot@1.0.35': + '@github/copilot@1.0.36': optionalDependencies: - '@github/copilot-darwin-arm64': 1.0.35 - '@github/copilot-darwin-x64': 1.0.35 - '@github/copilot-linux-arm64': 1.0.35 - '@github/copilot-linux-x64': 1.0.35 - '@github/copilot-win32-arm64': 1.0.35 - '@github/copilot-win32-x64': 1.0.35 + '@github/copilot-darwin-arm64': 1.0.36 + '@github/copilot-darwin-x64': 1.0.36 + '@github/copilot-linux-arm64': 1.0.36 + '@github/copilot-linux-x64': 1.0.36 + '@github/copilot-win32-arm64': 1.0.36 + '@github/copilot-win32-x64': 1.0.36 '@types/node@25.6.0': dependencies: @@ -434,55 +434,55 @@ snapshots: '@esbuild/win32-ia32': 0.28.0 '@esbuild/win32-x64': 0.28.0 - opencode-ai@1.14.22: + opencode-ai@1.14.28: optionalDependencies: - opencode-darwin-arm64: 1.14.22 - opencode-darwin-x64: 1.14.22 - opencode-darwin-x64-baseline: 1.14.22 - opencode-linux-arm64: 1.14.22 - opencode-linux-arm64-musl: 1.14.22 - opencode-linux-x64: 1.14.22 - opencode-linux-x64-baseline: 1.14.22 - opencode-linux-x64-baseline-musl: 1.14.22 - opencode-linux-x64-musl: 1.14.22 - opencode-windows-arm64: 1.14.22 - opencode-windows-x64: 1.14.22 - opencode-windows-x64-baseline: 1.14.22 + opencode-darwin-arm64: 1.14.28 + opencode-darwin-x64: 1.14.28 + opencode-darwin-x64-baseline: 1.14.28 + opencode-linux-arm64: 1.14.28 + opencode-linux-arm64-musl: 1.14.28 + opencode-linux-x64: 1.14.28 + opencode-linux-x64-baseline: 1.14.28 + opencode-linux-x64-baseline-musl: 1.14.28 + opencode-linux-x64-musl: 1.14.28 + opencode-windows-arm64: 1.14.28 + opencode-windows-x64: 1.14.28 + opencode-windows-x64-baseline: 1.14.28 - opencode-darwin-arm64@1.14.22: + opencode-darwin-arm64@1.14.28: optional: true - opencode-darwin-x64-baseline@1.14.22: + opencode-darwin-x64-baseline@1.14.28: optional: true - opencode-darwin-x64@1.14.22: + opencode-darwin-x64@1.14.28: optional: true - opencode-linux-arm64-musl@1.14.22: + opencode-linux-arm64-musl@1.14.28: optional: true - opencode-linux-arm64@1.14.22: + opencode-linux-arm64@1.14.28: optional: true - opencode-linux-x64-baseline-musl@1.14.22: + opencode-linux-x64-baseline-musl@1.14.28: optional: true - opencode-linux-x64-baseline@1.14.22: + opencode-linux-x64-baseline@1.14.28: optional: true - opencode-linux-x64-musl@1.14.22: + opencode-linux-x64-musl@1.14.28: optional: true - opencode-linux-x64@1.14.22: + opencode-linux-x64@1.14.28: optional: true - opencode-windows-arm64@1.14.22: + opencode-windows-arm64@1.14.28: optional: true - opencode-windows-x64-baseline@1.14.22: + opencode-windows-x64-baseline@1.14.28: optional: true - opencode-windows-x64@1.14.22: + opencode-windows-x64@1.14.28: optional: true undici-types@7.19.2: {} From 4e1968bbab61ae07e6cf60b680468ef718ad2c33 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 28 Apr 2026 00:01:46 +0200 Subject: [PATCH 13/17] :paperclip: Add updated version of github cli to devenv --- docker/devenv/Dockerfile | 29 ++++++++++++++++++++++++++--- docker/devenv/files/bashrc | 2 +- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index b83ff6a79d..de8bd3a4f7 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -184,7 +184,8 @@ FROM base AS setup-utils ENV CLJKONDO_VERSION=2026.04.15 \ BABASHKA_VERSION=1.12.208 \ CLJFMT_VERSION=0.16.4 \ - PIXI_VERSION=0.67.2 + PIXI_VERSION=0.67.2 \ + GITHUB_CLI_VERSION=2.91.0 RUN set -ex; \ ARCH="$(dpkg --print-architecture)"; \ @@ -267,6 +268,28 @@ RUN set -ex; \ tar -xf /tmp/cljfmt.tar.gz; \ rm -rf /tmp/cljfmt.tar.gz; + +RUN set -ex; \ + ARCH="$(dpkg --print-architecture)"; \ + case "${ARCH}" in \ + aarch64|arm64) \ + BINARY_URL="https://github.com/cli/cli/releases/download/v${GITHUB_CLI_VERSION}/gh_${GITHUB_CLI_VERSION}_linux_arm64.tar.gz"; \ + ;; \ + amd64|x86_64) \ + BINARY_URL="https://github.com/cli/cli/releases/download/v${GITHUB_CLI_VERSION}/gh_${GITHUB_CLI_VERSION}_linux_amd64.tar.gz"; \ + ;; \ + *) \ + echo "Unsupported arch: ${ARCH}"; \ + exit 1; \ + ;; \ + esac; \ + cd /tmp; \ + curl -LfsSo /tmp/gh.tar.gz ${BINARY_URL}; \ + mkdir /opt/gh; \ + cd /opt/gh; \ + tar -xf /tmp/gh.tar.gz; \ + rm -rf /tmp/gh.tar.gz; + # Install minio client RUN set -ex; \ ARCH="$(dpkg --print-architecture)"; \ @@ -310,7 +333,6 @@ RUN set -ex; \ nginx \ fd-find \ bat \ - gh \ \ fontconfig \ woff-tools \ @@ -399,13 +421,14 @@ ENV LANG='C.UTF-8' \ JAVA_HOME="/opt/jdk" \ CARGO_HOME="/opt/cargo" \ RUSTUP_HOME="/opt/rustup" \ - PATH="/opt/jdk/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH" + PATH="/opt/jdk/bin:/opt/gh/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH" COPY --from=penpotapp/imagemagick:7.1.2-13 /opt/imagick /opt/imagick COPY --from=setup-jvm /opt/jdk /opt/jdk COPY --from=setup-jvm /opt/clojure /opt/clojure COPY --from=setup-node /opt/node /opt/node COPY --from=setup-utils /opt/utils /opt/utils +COPY --from=setup-utils /opt/gh /opt/gh COPY --from=setup-rust /opt/cargo /opt/cargo COPY --from=setup-rust /opt/rustup /opt/rustup COPY --from=setup-rust /opt/emsdk /opt/emsdk diff --git a/docker/devenv/files/bashrc b/docker/devenv/files/bashrc index 98fc4a96dc..dc016b3481 100644 --- a/docker/devenv/files/bashrc +++ b/docker/devenv/files/bashrc @@ -2,7 +2,7 @@ EMSDK_QUIET=1 . /opt/emsdk/emsdk_env.sh; -export PATH="/home/penpot/.cargo/bin:/opt/jdk/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH" +export PATH="/home/penpot/.cargo/bin:/opt/jdk/bin:/opt/gh/bin:/opt/utils/bin:/opt/clojure/bin:/opt/node/bin:/opt/imagick/bin:/opt/cargo/bin:$PATH" export CARGO_HOME="/home/penpot/.cargo" alias l='ls --color -GFlh' From d9f099841a31c5a28111e456adb56547ebe48005 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 28 Apr 2026 09:23:56 +0200 Subject: [PATCH 14/17] :fire: Remove redundant mf/props metadata from modern components (#9192) The ::mf/props and ::mf/wrap-props metadata keys are no-ops on modern components (those defined with mf/defc and the * suffix) since the * suffix already triggers the props behavior these keys attempt to configure. This cleanup removes the redundant metadata from modern components across all UI directories. Changes: - comments/: comments - dashboard/: comments, deleted, files, fonts, grid, import, libraries, pin_button, projects, search, sidebar, subscription, team, templates - exports/: files - modal/: modal - settings/: subscription - static/: static - viewer/: comments, interactions, viewer - workspace/: context_menu, libraries, sidebar/assets, viewport/gradients, tokens/settings/menu --- frontend/src/app/main/ui/comments.cljs | 9 ++-- frontend/src/app/main/ui/dashboard.cljs | 3 +- .../src/app/main/ui/dashboard/comments.cljs | 1 - .../src/app/main/ui/dashboard/deleted.cljs | 3 +- frontend/src/app/main/ui/dashboard/files.cljs | 4 +- frontend/src/app/main/ui/dashboard/fonts.cljs | 12 ++--- frontend/src/app/main/ui/dashboard/grid.cljs | 5 +- .../src/app/main/ui/dashboard/import.cljs | 3 +- .../src/app/main/ui/dashboard/libraries.cljs | 1 - .../src/app/main/ui/dashboard/pin_button.cljs | 1 - .../src/app/main/ui/dashboard/projects.cljs | 8 +-- .../src/app/main/ui/dashboard/search.cljs | 1 - .../src/app/main/ui/dashboard/sidebar.cljs | 12 ++--- .../app/main/ui/dashboard/subscription.cljs | 1 - frontend/src/app/main/ui/dashboard/team.cljs | 31 +++-------- .../src/app/main/ui/dashboard/templates.cljs | 4 +- frontend/src/app/main/ui/exports/files.cljs | 3 +- frontend/src/app/main/ui/modal.cljs | 4 +- .../app/main/ui/settings/subscription.cljs | 1 - frontend/src/app/main/ui/static.cljs | 3 -- frontend/src/app/main/ui/viewer.cljs | 1 - frontend/src/app/main/ui/viewer/comments.cljs | 1 - .../src/app/main/ui/viewer/interactions.cljs | 4 +- .../app/main/ui/workspace/context_menu.cljs | 52 ++++++------------- .../src/app/main/ui/workspace/libraries.cljs | 12 ++--- .../app/main/ui/workspace/sidebar/assets.cljs | 1 - .../ui/workspace/tokens/settings/menu.cljs | 1 - .../main/ui/workspace/viewport/gradients.cljs | 4 +- 28 files changed, 51 insertions(+), 135 deletions(-) diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index 8f34c41277..cc12398c62 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -541,8 +541,7 @@ [:div {:class (stl/css :comments-mentions-email)} email]]))]))) (mf/defc mentions-button* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [] (let [mentions-s (mf/use-ctx mentions-context) display-mentions* (mf/use-state false) @@ -647,8 +646,7 @@ [:span {:class (stl/css :replies-unread)} (str unread-replies " " (tr "labels.replies.new"))]))])]]) (mf/defc comment-form-buttons* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [on-submit on-cancel is-disabled]}] (let [handle-cancel (mf/use-fn @@ -684,8 +682,7 @@ (> (count content) 750)) (mf/defc comment-reply-form* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [on-submit]}] (let [content (mf/use-state "") diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index 04c376def4..5962ecacf3 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -48,8 +48,7 @@ [rumext.v2 :as mf])) (mf/defc dashboard-content* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [team projects project section search-term profile default-project]}] (let [container (mf/use-ref) content-width (mf/use-state 0) diff --git a/frontend/src/app/main/ui/dashboard/comments.cljs b/frontend/src/app/main/ui/dashboard/comments.cljs index e432a642c8..00da33d2d4 100644 --- a/frontend/src/app/main/ui/dashboard/comments.cljs +++ b/frontend/src/app/main/ui/dashboard/comments.cljs @@ -25,7 +25,6 @@ (deprecated-icon/icon-xref :comments (stl/css :comments-icon))) (mf/defc comments-icon* - {::mf/props :obj} [{:keys [profile on-show-comments]}] (let [threads-map (mf/deref refs/comment-threads) diff --git a/frontend/src/app/main/ui/dashboard/deleted.cljs b/frontend/src/app/main/ui/dashboard/deleted.cljs index 7ad6f2f374..62033f707b 100644 --- a/frontend/src/app/main/ui/dashboard/deleted.cljs +++ b/frontend/src/app/main/ui/dashboard/deleted.cljs @@ -54,8 +54,7 @@ :on-accept accept-fn})))) (mf/defc header* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [] [:header {:class (stl/css :dashboard-header) :data-testid "dashboard-header"} [:div#dashboard-deleted-title {:class (stl/css :dashboard-title)} diff --git a/frontend/src/app/main/ui/dashboard/files.cljs b/frontend/src/app/main/ui/dashboard/files.cljs index 797217dbc0..2a7e9e1193 100644 --- a/frontend/src/app/main/ui/dashboard/files.cljs +++ b/frontend/src/app/main/ui/dashboard/files.cljs @@ -31,8 +31,7 @@ (deprecated-icon/icon-xref :menu (stl/css :menu-icon))) (mf/defc header* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [project create-fn can-edit]}] (let [project-id (:id project) @@ -133,7 +132,6 @@ :on-import on-import}])]])) (mf/defc files-section* - {::mf/props :obj} [{:keys [project team]}] (let [files (mf/deref refs/files) project-id (get project :id) diff --git a/frontend/src/app/main/ui/dashboard/fonts.cljs b/frontend/src/app/main/ui/dashboard/fonts.cljs index ad90465c4e..72c57856b9 100644 --- a/frontend/src/app/main/ui/dashboard/fonts.cljs +++ b/frontend/src/app/main/ui/dashboard/fonts.cljs @@ -54,8 +54,7 @@ (str/blank? (:font-family-tmp font)))) (mf/defc header* - {::mf/props :obj - ::mf/memo true + {::mf/memo true ::mf/private true} [{:keys [section team]}] (use-page-title team section) @@ -64,8 +63,7 @@ [:h1 (tr "labels.fonts")]]]) (mf/defc font-variant-display-name* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [variant]}] [:* [:span (cm/font-weight->name (:font-weight variant))] @@ -73,8 +71,7 @@ [:span " " (str/capital (:font-style variant))])]) (mf/defc uploaded-fonts* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [team installed-fonts]}] (let [fonts* (mf/use-state {}) fonts (deref fonts*) @@ -438,7 +435,6 @@ :on-edit on-edit}]]))])) (mf/defc installed-fonts* - {::mf/props :obj} [{:keys [fonts can-edit]}] (let [sterm (mf/use-state "") @@ -491,7 +487,6 @@ (l/derived :fonts st/state)) (mf/defc fonts-page* - {::mf/props :obj} [{:keys [team]}] (let [fonts (mf/deref ref:fonts) permissions (:permissions team) @@ -505,7 +500,6 @@ {:team team :fonts fonts :can-edit can-edit}]]])) (mf/defc font-providers-page* - {::mf/props :obj} [{:keys [team]}] [:* [:> header* {:team team :section :providers}] diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index a24490af59..1396986e06 100644 --- a/frontend/src/app/main/ui/dashboard/grid.cljs +++ b/frontend/src/app/main/ui/dashboard/grid.cljs @@ -84,8 +84,7 @@ (rx/mapcat (partial persist-thumbnail file-id revn)))) (mf/defc grid-item-thumbnail* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [can-edit file can-restore]}] (let [file-id (get file :id) revn (get file :revn) @@ -131,7 +130,6 @@ (deprecated-icon/icon-xref :menu (stl/css :menu-icon))) (mf/defc grid-item-library* - {::mf/props :obj} [{:keys [file can-restore]}] (mf/with-effect [file] (when file @@ -466,7 +464,6 @@ :can-restore can-restore}]])]]]]])) (mf/defc grid* - {::mf/props :obj} [{:keys [files project origin limit create-fn can-edit selected-files can-restore]}] (let [dragging? (mf/use-state false) project-id (get project :id) diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index 1510af2455..5f5dd533b8 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -192,8 +192,7 @@ (swap! state update-entry-status message)))))) (mf/defc import-entry* - {::mf/props :obj - ::mf/memo true + {::mf/memo true ::mf/private true} [{:keys [entries entry edition can-be-deleted on-edit on-change on-delete]}] (let [status (:status entry) diff --git a/frontend/src/app/main/ui/dashboard/libraries.cljs b/frontend/src/app/main/ui/dashboard/libraries.cljs index 8e265914dd..53908cd86a 100644 --- a/frontend/src/app/main/ui/dashboard/libraries.cljs +++ b/frontend/src/app/main/ui/dashboard/libraries.cljs @@ -27,7 +27,6 @@ st/state)) (mf/defc libraries-page* - {::mf/props :obj} [{:keys [team default-project]}] (let [files (mf/deref refs/shared-files) diff --git a/frontend/src/app/main/ui/dashboard/pin_button.cljs b/frontend/src/app/main/ui/dashboard/pin_button.cljs index ffa76fb6c3..5927e0493c 100644 --- a/frontend/src/app/main/ui/dashboard/pin_button.cljs +++ b/frontend/src/app/main/ui/dashboard/pin_button.cljs @@ -18,7 +18,6 @@ (deprecated-icon/icon-xref :pin (stl/css :icon))) (mf/defc pin-button* - {::mf/props :obj} [{:keys [aria-label is-pinned class] :as props}] (let [aria-label (or aria-label (tr "dashboard.pin-unpin")) class (dm/str (or class "") " " (stl/css-case :button true :button-active is-pinned)) diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs index 04ca802352..e29b8bbdb0 100644 --- a/frontend/src/app/main/ui/dashboard/projects.cljs +++ b/frontend/src/app/main/ui/dashboard/projects.cljs @@ -48,7 +48,6 @@ (mf/defc header* {::mf/wrap [mf/memo] - ::mf/props :obj ::mf/private true} [{:keys [can-edit]}] (let [on-click (mf/use-fn #(st/emit! (dd/create-project)))] @@ -62,8 +61,7 @@ (tr "dashboard.new-project")])])) (mf/defc team-hero* - {::mf/wrap [mf/memo] - ::mf/props :obj} + {::mf/wrap [mf/memo]} [{:keys [team on-close]}] (let [on-nav-members-click (mf/use-fn #(st/emit! (dcm/go-to-dashboard-members))) @@ -102,8 +100,7 @@ close-icon]])) (mf/defc project-item* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [project is-first team files can-edit]}] (let [project-id (get project :id) team-id (get team :id) @@ -313,7 +310,6 @@ (l/derived :recent-files st/state)) (mf/defc projects-section* - {::mf/props :obj} [{:keys [team projects profile]}] (let [team-id (get team :id) diff --git a/frontend/src/app/main/ui/dashboard/search.cljs b/frontend/src/app/main/ui/dashboard/search.cljs index 126b038ca8..1c2b6ddb20 100644 --- a/frontend/src/app/main/ui/dashboard/search.cljs +++ b/frontend/src/app/main/ui/dashboard/search.cljs @@ -32,7 +32,6 @@ st/state)) (mf/defc search-page* - {::mf/props :obj} [{:keys [team search-term]}] (let [search-term (d/nilv search-term "") diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs index c0d9b46496..66604f3f73 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.cljs +++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs @@ -967,8 +967,7 @@ [:div {:class (stl/css-case :separator true :overflow-separator overflow?)}]]])) (mf/defc help-learning-menu* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [on-close on-click]}] (let [handle-click-url (mf/use-fn @@ -1012,8 +1011,7 @@ (tr "labels.give-feedback")])])) (mf/defc community-contributions-menu* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [on-close]}] (let [handle-click-url (mf/use-fn @@ -1043,8 +1041,7 @@ (tr "labels.community")]])) (mf/defc about-penpot-menu* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [on-close]}] (let [version cf/version show-release-notes @@ -1276,8 +1273,7 @@ nil))])) (mf/defc sidebar* - {::mf/props :obj - ::mf/wrap [mf/memo]} + {::mf/wrap [mf/memo]} [{:keys [team profile] :as props}] [:nav {:class (stl/css :dashboard-sidebar) :data-testid "dashboard-sidebar"} [:> sidebar-content* props] diff --git a/frontend/src/app/main/ui/dashboard/subscription.cljs b/frontend/src/app/main/ui/dashboard/subscription.cljs index 8749305c1d..a07dbe0649 100644 --- a/frontend/src/app/main/ui/dashboard/subscription.cljs +++ b/frontend/src/app/main/ui/dashboard/subscription.cljs @@ -118,7 +118,6 @@ :is-highlighted false}])))) (mf/defc nitrate-sidebar* - {::mf/props :obj} [{:keys [profile teams]}] (let [nitrate? (dnt/is-valid-license? profile) orgs (mf/with-memo [teams] diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 993b1623a8..e492b9a20d 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -364,8 +364,7 @@ (st/emit! (dtm/update-member-role params)))) (mf/defc team-member* - {::mf/wrap [mf/memo] - ::mf/props :obj} + {::mf/wrap [mf/memo]} [{:keys [team member total-members profile]}] (let [member-id (:id member) @@ -493,8 +492,7 @@ :on-leave on-leave'}]]])) (mf/defc team-members* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [team profile]}] (let [members (get team :members) @@ -533,7 +531,6 @@ :total-members total-members}])]])) (mf/defc team-members-page* - {::mf/props :obj} [{:keys [team profile]}] (mf/with-effect [team] (dom/set-html-title @@ -562,7 +559,6 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (mf/defc invitation-role-selector* - {::mf/props :obj} [{:keys [can-invite role status on-change]}] (let [show? (mf/use-state false) label (cond @@ -608,8 +604,7 @@ (tr "labels.viewer")]]]])) (mf/defc invitation-actions* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [invitation team-id]}] (let [email (:email invitation) on-error @@ -658,8 +653,7 @@ (mf/defc invitation-row* {::mf/wrap [mf/memo] - ::mf/private true - ::mf/props :obj} + ::mf/private true} [{:keys [invitation can-invite team-id selected on-select-change]}] (let [expired? (:expired invitation) @@ -725,8 +719,7 @@ :team-id team-id}])]])) (mf/defc empty-invitation-table* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [can-invite team]}] (let [route (mf/deref refs/route) @@ -793,8 +786,7 @@ (tr "labels.resend"))]]]]]) (mf/defc invitation-section* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [team]}] (let [permissions (get team :permissions) invitations (mf/use-state (get team :invitations)) @@ -978,7 +970,6 @@ :on-select-change on-select-change}])])])) (mf/defc team-invitations-page* - {::mf/props :obj} [{:keys [team profile]}] (mf/with-effect [team] @@ -1132,7 +1123,6 @@ (tr "modals.create-webhook.submit-label"))}]]]]]])) (mf/defc webhooks-hero* - {::mf/props :obj} [] [:div {:class (stl/css :webhooks-hero-container)} [:h2 {:class (stl/css :hero-title)} @@ -1144,8 +1134,7 @@ (tr "dashboard.webhooks.create")]]) (mf/defc webhook-actions* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [on-edit on-delete can-edit]}] (let [show? (mf/use-state false) on-show (mf/use-fn #(reset! show? true)) @@ -1168,7 +1157,6 @@ (mf/defc webhook-item* {::mf/wrap [mf/memo] - ::mf/props :obj ::mf/private true} [{:keys [webhook permissions]}] (let [error-code (:error-code webhook) @@ -1234,8 +1222,7 @@ :can-edit can-edit}]]])) (mf/defc webhooks-list* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [webhooks permissions]}] [:div {:class (stl/css :table-rows :webhook-table)} (for [webhook webhooks] @@ -1245,7 +1232,6 @@ :permissions permissions}])]) (mf/defc webhooks-page* - {::mf/props :obj} [{:keys [team]}] (let [webhooks (:webhooks team)] @@ -1277,7 +1263,6 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (mf/defc team-settings-page* - {::mf/props :obj} [{:keys [team]}] (let [finput (mf/use-ref) diff --git a/frontend/src/app/main/ui/dashboard/templates.cljs b/frontend/src/app/main/ui/dashboard/templates.cljs index d5f84c2862..8abccfc3e3 100644 --- a/frontend/src/app/main/ui/dashboard/templates.cljs +++ b/frontend/src/app/main/ui/dashboard/templates.cljs @@ -64,8 +64,7 @@ :on-finish-import on-finish})))) (mf/defc title* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [on-click is-collapsed]}] (let [on-key-down (mf/use-fn @@ -171,7 +170,6 @@ [:div {:class (stl/css :template-link-text)} (tr "dashboard.libraries-and-templates.explore")]]]]]])) (mf/defc templates-section* - {::mf/props :obj} [{:keys [default-project-id profile project-id team-id]}] (let [templates (mf/deref builtin-templates) templates (mf/with-memo [templates] diff --git a/frontend/src/app/main/ui/exports/files.cljs b/frontend/src/app/main/ui/exports/files.cljs index 60f19737e5..9103852613 100644 --- a/frontend/src/app/main/ui/exports/files.cljs +++ b/frontend/src/app/main/ui/exports/files.cljs @@ -45,8 +45,7 @@ :files files})) (mf/defc export-entry* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [file]}] [:div {:class (stl/css-case :file-entry true diff --git a/frontend/src/app/main/ui/modal.cljs b/frontend/src/app/main/ui/modal.cljs index 6e9b1df7d4..4210d9ba40 100644 --- a/frontend/src/app/main/ui/modal.cljs +++ b/frontend/src/app/main/ui/modal.cljs @@ -46,8 +46,7 @@ (st/emit! (modal/hide))))) (mf/defc modal-wrapper* - {::mf/props :obj - ::mf/wrap [mf/memo]} + {::mf/wrap [mf/memo]} [{:keys [data]}] (let [wrapper-ref (mf/use-ref nil) components (mf/deref modal/components) @@ -82,7 +81,6 @@ (l/derived ::modal/modal st/state)) (mf/defc modal-container* - {::mf/props :obj} [] (let [container (hooks/use-portal-container :modal)] (when-let [modal (mf/deref ref:modal)] diff --git a/frontend/src/app/main/ui/settings/subscription.cljs b/frontend/src/app/main/ui/settings/subscription.cljs index 65c1eccf95..f9441aca1c 100644 --- a/frontend/src/app/main/ui/settings/subscription.cljs +++ b/frontend/src/app/main/ui/settings/subscription.cljs @@ -25,7 +25,6 @@ [rumext.v2 :as mf])) (mf/defc plan-card* - {::mf/props :obj} [{:keys [card-title card-title-icon price-value price-period diff --git a/frontend/src/app/main/ui/static.cljs b/frontend/src/app/main/ui/static.cljs index b5337616f6..a13d6f76d8 100644 --- a/frontend/src/app/main/ui/static.cljs +++ b/frontend/src/app/main/ui/static.cljs @@ -41,7 +41,6 @@ (def TimeoutError rxjs/TimeoutError) (mf/defc error-container* - {::mf/props :obj} [{:keys [children]}] (let [profile-id (:profile-id @st/state) on-nav-root (mf/use-fn #(st/emit! (rt/nav-root)))] @@ -186,7 +185,6 @@ [:& recovery-sent-page {:email @user-email}]])]]])) (mf/defc request-dialog* - {::mf/props :obj} [{:keys [title content button-text on-button-click cancel-text on-close]}] (let [on-click (or on-button-click on-close)] [:div {:class (stl/css :overlay)} @@ -532,7 +530,6 @@ children]) (mf/defc exception-page* - {::mf/props :obj} [{:keys [data route] :as props}] (let [type (:type data) diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs index 8ef3e26056..47e6d2aa2b 100644 --- a/frontend/src/app/main/ui/viewer.cljs +++ b/frontend/src/app/main/ui/viewer.cljs @@ -278,7 +278,6 @@ :zoom zoom}])]]) (mf/defc viewer-content* - {::mf/props :obj} [{:keys [data page-id share-id section index interactions-mode share]}] (let [{:keys [file users project permissions]} data allowed (or diff --git a/frontend/src/app/main/ui/viewer/comments.cljs b/frontend/src/app/main/ui/viewer/comments.cljs index 9646c81f8c..ffc3992366 100644 --- a/frontend/src/app/main/ui/viewer/comments.cljs +++ b/frontend/src/app/main/ui/viewer/comments.cljs @@ -235,7 +235,6 @@ :zoom zoom}])]]])) (mf/defc comments-sidebar* - {::mf/props :obj} [{:keys [profiles frame page]}] (let [profile (mf/deref refs/profile) local (mf/deref refs/comments-local) diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs index ae4bec1f1e..be73931031 100644 --- a/frontend/src/app/main/ui/viewer/interactions.cljs +++ b/frontend/src/app/main/ui/viewer/interactions.cljs @@ -219,8 +219,7 @@ :fixed? fixed?}])) (mf/defc flows-menu* - {::mf/wrap [mf/memo] - ::mf/props :obj} + {::mf/wrap [mf/memo]} [{:keys [page index]}] (let [flows (not-empty (:flows page)) frames (:frames page) @@ -268,7 +267,6 @@ [:span {:class (stl/css :icon)} deprecated-icon/tick])])]]]))) (mf/defc interactions-menu* - {::mf/props :obj} [{:keys [interactions-mode]}] (let [show-dropdown? (mf/use-state false) toggle-dropdown (mf/use-fn #(swap! show-dropdown? not)) diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 1703cee2f1..c310b073be 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -54,8 +54,7 @@ (dom/stop-propagation event)) (mf/defc menu-entry* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [title shortcut on-click on-pointer-enter on-pointer-leave on-unmount children is-selected icon disabled value]}] (let [submenu-ref (mf/use-ref nil) @@ -142,14 +141,12 @@ children])]))) (mf/defc menu-separator* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [] [:li {:class (stl/css :separator)}]) (mf/defc context-menu-edit* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes]}] (let [multiple? (> (count shapes) 1) @@ -251,8 +248,7 @@ [:> menu-separator* {}]])) (mf/defc context-menu-layer-position* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes]}] (let [do-bring-forward (mf/use-fn #(st/emit! (dw/vertical-order-selected :up))) do-bring-to-front (mf/use-fn #(st/emit! (dw/vertical-order-selected :top))) @@ -298,8 +294,7 @@ [:> menu-separator* {}]])) (mf/defc context-menu-flip* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [] (let [do-flip-vertical #(st/emit! (dw/flip-vertical-selected)) do-flip-horizontal #(st/emit! (dw/flip-horizontal-selected))] @@ -314,8 +309,7 @@ [:> menu-separator* {}]])) (mf/defc context-menu-thumbnail* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes]}] (let [single? (= (count shapes) 1) has-frame? (some cfh/frame-shape? shapes) @@ -331,8 +325,7 @@ [:> menu-separator* {}]]))) (mf/defc context-menu-rename* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes]}] (let [do-rename #(st/emit! (dw/start-rename-selected))] (when (= (count shapes) 1) @@ -343,8 +336,7 @@ :on-click do-rename}]]))) (mf/defc context-menu-group* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes]}] (let [multiple? (> (count shapes) 1) single? (= (count shapes) 1) @@ -397,8 +389,7 @@ [:> menu-separator* {}]])])) (mf/defc context-focus-mode-menu* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [] (let [focus (mf/deref refs/workspace-focus-selected) do-toggle-focus-mode #(st/emit! (dw/toggle-focus-mode))] @@ -410,8 +401,7 @@ :on-click do-toggle-focus-mode}])) (mf/defc context-menu-path* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes objects disable-flatten disable-booleans]}] (let [multiple? (> (count shapes) 1) single? (= (count shapes) 1) @@ -488,8 +478,7 @@ :on-click do-transform-to-path}]])])])) (mf/defc context-menu-layer-options* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes]}] (let [ids (mapv :id shapes) do-show-shape #(st/emit! (dw/update-shape-flags ids {:hidden false})) @@ -514,8 +503,7 @@ :on-click do-lock-shape}])])) (mf/defc context-menu-prototype* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes]}] (let [flows (mf/deref refs/workspace-page-flows) options-mode (mf/deref refs/options-mode-global) @@ -536,8 +524,7 @@ :on-click do-add-flow}])))) (mf/defc context-menu-layout* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [shapes]}] (let [single? (= (count shapes) 1) objects (deref refs/workspace-page-objects) @@ -647,8 +634,7 @@ :on-click do-combine-as-variants}]])])) (mf/defc context-menu-delete* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [] (let [do-delete #(st/emit! (dw/delete-selected))] [:* @@ -690,8 +676,7 @@ [:> context-menu-delete* props]]))) (mf/defc page-item-context-menu* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [mdata]}] (let [page (:page mdata) deletable? (:deletable? mdata) @@ -718,7 +703,6 @@ :on-click do-duplicate}]])) (mf/defc viewport-context-menu* - {::mf/props :obj} [] (let [focus (mf/deref refs/workspace-focus-selected) read-only? (mf/use-ctx ctx/workspace-read-only?) @@ -741,8 +725,7 @@ :on-click do-toggle-focus-mode}])])) (mf/defc grid-track-context-menu* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [mdata]}] (let [{:keys [type index grid-id]} mdata do-delete-track @@ -791,8 +774,7 @@ [:> menu-entry* {:title (tr "workspace.context-menu.grid-track.row.delete-shapes") :on-click do-delete-track-shapes}]]))) (mf/defc grid-cells-context-menu* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [mdata]}] (let [{:keys [grid cells]} mdata diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index 99d0e9c3de..08dad36701 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -112,8 +112,7 @@ "\u00A0"))) (mf/defc library-description* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [summary]}] (let [components-count (get summary :components) graphics-count (get summary :graphics) @@ -138,8 +137,7 @@ (tr "workspace.libraries.typography" (c typography-count))])])) (mf/defc sample-library-entry* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [library importing]}] (let [id (:id library) importing? (deref importing) @@ -190,8 +188,7 @@ (not (ctob/empty-lib? tokens-lib)))) (mf/defc libraries-tab* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [is-shared linked-libraries shared-libraries]}] (let [file-id (mf/use-ctx ctx/current-file-id) search-term* (mf/use-state "") @@ -488,8 +485,7 @@ :typographies typographies}])) (mf/defc updates-tab* - {::mf/props :obj - ::mf/private true} + {::mf/private true} [{:keys [file-id libraries]}] ;; FIXME: naming (let [summary?* (mf/use-state true) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index dc7fd0a50d..f4082ac55b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -29,7 +29,6 @@ (mf/defc assets-libraries* {::mf/wrap [mf/memo] - ::mf/props :obj ::mf/private true} [{:keys [filters]}] (let [file-id (mf/use-ctx ctx/current-file-id) diff --git a/frontend/src/app/main/ui/workspace/tokens/settings/menu.cljs b/frontend/src/app/main/ui/workspace/tokens/settings/menu.cljs index 8469d83f28..a26400d7cd 100644 --- a/frontend/src/app/main/ui/workspace/tokens/settings/menu.cljs +++ b/frontend/src/app/main/ui/workspace/tokens/settings/menu.cljs @@ -26,7 +26,6 @@ [rumext.v2 :as mf])) (mf/defc token-settings* - {::mf/wrap-props false} [] (let [file-data (deref refs/workspace-data) base-font-size* (mf/use-state #(ctf/get-base-font-size file-data)) diff --git a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs index cd2623fd47..525a4fd7bb 100644 --- a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs @@ -448,7 +448,6 @@ :fill "var(--app-white)"}]))])) (mf/defc gradient-handlers-impl* - {::mf/props :obj} [{:keys [zoom stops gradient editing shape]}] (let [transform (gsh/transform-matrix shape) transform-inverse (gsh/inverse-transform-matrix shape) @@ -517,8 +516,7 @@ :on-change-width on-change-width}])) (mf/defc gradient-handlers* - {::mf/wrap [mf/memo] - ::mf/props :obj} + {::mf/wrap [mf/memo]} [{:keys [id zoom]}] (let [shape-ref (mf/use-memo (mf/deps id) #(refs/object-by-id id)) shape (mf/deref shape-ref) From a35b61ee0c7618acaa9e03cdef5c9241b8e05919 Mon Sep 17 00:00:00 2001 From: Luis de Dios Date: Tue, 28 Apr 2026 09:26:15 +0200 Subject: [PATCH 15/17] :bug: Fix put onboarding modals of top of libraries & templates panel (#9178) --- frontend/src/app/main/ui/dashboard/templates.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/main/ui/dashboard/templates.scss b/frontend/src/app/main/ui/dashboard/templates.scss index 5d58ac4fea..f3323c58f2 100644 --- a/frontend/src/app/main/ui/dashboard/templates.scss +++ b/frontend/src/app/main/ui/dashboard/templates.scss @@ -27,7 +27,7 @@ transition: bottom 300ms; width: calc(100% - $sz-12); pointer-events: none; - z-index: var(--z-index-set); + z-index: var(--z-index-panels); &.collapsed { inset-block-end: calc(-1 * px2rem(228)); From aabdb692183f907d6fbfecbd77017db16fe75897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?andr=C3=A9s=20gonz=C3=A1lez?= Date: Tue, 28 Apr 2026 09:37:40 +0200 Subject: [PATCH 16/17] :books: Update MCP docs for public release (#9184) --- docs/contributing-guide/index.njk | 4 +-- docs/mcp/index.md | 47 +++++++++---------------------- docs/user-guide/index.njk | 2 +- 3 files changed, 17 insertions(+), 36 deletions(-) diff --git a/docs/contributing-guide/index.njk b/docs/contributing-guide/index.njk index e0bb204487..74574e4975 100644 --- a/docs/contributing-guide/index.njk +++ b/docs/contributing-guide/index.njk @@ -2,7 +2,7 @@ title: Contributing desc: Learn how to contribute to Penpot, the open-source design collaboration platform! Find guides on bug reporting, translations, code contributions, and more. eleventyNavigation: - key: Contributing + key: Contribute order: 3 --- @@ -10,7 +10,7 @@ eleventyNavigation: User guide -

Contributing guide.

+

Contributing guide

In this documentation you will find (almost) everything you need to know about how to contribute at Penpot.

diff --git a/docs/mcp/index.md b/docs/mcp/index.md index c338faf96d..d473eff10c 100644 --- a/docs/mcp/index.md +++ b/docs/mcp/index.md @@ -2,6 +2,9 @@ title: Penpot MCP server order: 1 desc: Installing and using the Penpot MCP server with any AI agent or LLM you trust. +eleventyNavigation: + key: MCP Server + order: 6 ---
@@ -69,7 +72,7 @@ There are three key pieces: ### Basic concepts Some important concepts for users: -* **Integrations page**: MCP is configured under **Your account → Integrations → MCP Server (Beta)**. Here you enable or disable MCP, get the server URL and manage the MCP key. +* **Integrations page**: MCP is configured under **Your account → Integrations → MCP Server**. Here you enable or disable MCP, get the server URL and manage the MCP key. * **MCP key**: a personal, non-recoverable token that authenticates your AI client with the MCP server. Only one key can exist per user at a time. This is used by the remote MCP setup. * **Currently focused page**: MCP always operates on the page you have in focus in Penpot. If you change the focused page (even in another browser window), the MCP context follows that page. * **Active MCP tab**: MCP can only be active in one browser tab at a time. If you have Penpot open in several tabs, you choose explicitly which one owns MCP before running agents. @@ -110,32 +113,20 @@ If you just want to try Penpot MCP quickly, follow this path for the **hosted (r ### Remote MCP in 5 steps -
- -### Important: remote MCP is not in production yet - -Remote MCP is not available yet in Penpot production (`design.penpot.app`). It is planned for an upcoming release (currently targeted around **2.16**). - -Right now, the remote MCP flow is available only in **testing environments** (for example, instances deployed from the `staging` branch: https://github.com/penpot/penpot/tree/staging). - -If you need MCP in production today, use the **Local MCP server** setup instead. See [Local MCP server](#local-mcp-server). - -
- 1. #### Enable MCP in Penpot - Go to **Your account → Integrations → MCP Server (Beta)** and enable the feature. + Go to **Your account → Integrations → MCP Server** and enable the feature. - ![MCP Server (Beta) in Penpot Integrations, enable](/img/mcp/mcp-enable.webp) + ![MCP Server in Penpot Integrations, enable](/img/mcp/mcp-enable.webp) 2. #### Generate your MCP key If you do not have one yet, create it. The key is shown only once—store it safely. - ![MCP Server (Beta) in Penpot Integrations, generate key](/img/mcp/mcp-generate-key.webp) + ![MCP Server in Penpot Integrations, generate key](/img/mcp/mcp-generate-key.webp) 3. #### Copy the server URL In the same Integrations section, copy the **server URL** that already includes your MCP key as `userToken`. - ![MCP Server (Beta) in Penpot Integrations, copy server url](/img/mcp/mcp-server-url.webp) + ![MCP Server in Penpot Integrations, copy server url](/img/mcp/mcp-server-url.webp) 4. #### Add the server to your MCP client In your MCP-aware IDE/agent (Cursor, Claude Code, etc.), add a new server pointing to that URL. @@ -191,7 +182,7 @@ You can use Penpot MCP server in two main ways: * Hosted for you (no need to run anything on your machine). * Best option for most users, simpler installation and fewer moving parts. * Does **not** have privileged access to your local file system, it can only work with what Penpot exposes (design files, libraries, tokens, etc.). - * The **server URL** is provided in **Your account → Integrations → MCP Server (Beta)** and looks like: + * The **server URL** is provided in **Your account → Integrations → MCP Server** and looks like: * `https:///mcp/stream?userToken=YOUR_MCP_KEY` * The domain depends on the Penpot installation. In the official SaaS it will be `design.penpot.app`. * **Local MCP server** @@ -298,21 +289,11 @@ In Penpot, open a file and connect the plugin from **File → MCP Server → Con Remote MCP is the easiest way to start using AI agents with Penpot. It's hosted for you, so you don't need to install or run anything on your machine. -
- -### Availability note - -Remote MCP is currently available only in **testing environments**. It is not yet available in Penpot production (`design.penpot.app`) and is planned for an upcoming release (currently targeted around **2.16**). - -If you need MCP in production today, use the **Local MCP server** setup instead. See [Local MCP server](#local-mcp-server). - -
- ### Install and activate 1. Open **Your account → Integrations**. -2. In the **MCP Server (Beta)** section, read the short description to confirm that feature is available for your account. +2. In the **MCP Server** section, read the short description to confirm that feature is available for your account. 3. Use the **Status** toggle to enable MCP Server. Penpot remembers this state per user across sessions. 4. If this is your first time, Penpot will ask you to **generate an MCP key**. The key is shown only once, store it safely. * Treat the MCP key like a password/token: do not share it in screenshots, logs, or code samples. @@ -326,7 +307,7 @@ If you need MCP in production today, use the **Local MCP server** setup instead. For client-specific setup, use the shared section **Connect your MCP client**. -For remote mode, use the URL shown in **Your account → Integrations → MCP Server (Beta)**, which includes your `userToken`. +For remote mode, use the URL shown in **Your account → Integrations → MCP Server**, which includes your `userToken`. ### Use @@ -336,7 +317,7 @@ Once everything is configured, day-to-day use of Penpot MCP follows a simple pat #### Run 1. **Enable MCP** - * Go to **Your account → Integrations → MCP Server (Beta)** and set **Status** to **Enabled**. + * Go to **Your account → Integrations → MCP Server** and set **Status** to **Enabled**. 2. **Connect plugin**: * Open a design file and use **File → MCP Server → Connect**. 3. **Run prompts**: @@ -393,7 +374,7 @@ At a high level: 2. Start the MCP server and plugin server from your terminal: ```json -npx @penpot/mcp@beta +npx @penpot/mcp@stable ``` Leave this terminal running while you use MCP. @@ -426,7 +407,7 @@ Once everything is configured, day-to-day use of Penpot MCP follows a simple pat 1. **Start MCP** - Run `npx -y @penpot/mcp@stable` (production) or `npx -y @penpot/mcp@beta` (test), and keep that terminal running. + Run `npx -y @penpot/mcp@stable` (production), and keep that terminal running. 2. **Connect plugin** In Penpot, load `http://localhost:4400/manifest.json`, run the plugin, and click **Connect to MCP server**. diff --git a/docs/user-guide/index.njk b/docs/user-guide/index.njk index d017f151c2..4b932f7ac0 100644 --- a/docs/user-guide/index.njk +++ b/docs/user-guide/index.njk @@ -2,7 +2,7 @@ title: User guide desc: Learn everything from interface basics to advanced features like prototyping and design sharing with Penpot's comprehensive user guide! Free access. eleventyNavigation: - key: User guide + key: User Guide order: 2 --- From ad1111a613c9d30a71ea4cc39fd980aefc432cb4 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 27 Apr 2026 17:26:08 +0200 Subject: [PATCH 17/17] :bug: Fix problem with align center + grow auto-width --- .../src/app/main/ui/workspace/shapes/text/v2_editor.cljs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs index e81f841baf..6e96297ebc 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.cljs @@ -418,9 +418,10 @@ (cond-> #js {:pointerEvents "all"} render-wasm? (obj/merge! - #js {"--editor-container-width" (dm/str width "px") - "--editor-container-height" (dm/str height "px") - "--fallback-families" (if (seq fallback-families) (dm/str (str/join ", " fallback-families)) "sourcesanspro")}) + #js {"--editor-container-width" "auto" + "--editor-container-height" "auto" + "--fallback-families" (if (seq fallback-families) (dm/str (str/join ", " fallback-families)) "sourcesanspro") + :display "flex"}) (not render-wasm?) (obj/merge!