From 719f4a5035bb93ba045d8b6549c6a27a8e26a58e Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 21 Apr 2026 14:33:16 +0200 Subject: [PATCH 1/6] :bug: Fix default alignself behavior --- .../src/shapes/modifiers/flex_layout.rs | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/render-wasm/src/shapes/modifiers/flex_layout.rs b/render-wasm/src/shapes/modifiers/flex_layout.rs index 7661d93088..e4f0d29311 100644 --- a/render-wasm/src/shapes/modifiers/flex_layout.rs +++ b/render-wasm/src/shapes/modifiers/flex_layout.rs @@ -551,30 +551,27 @@ fn child_position( child_axis: &ChildAxis, track: &TrackData, ) -> Point { + let mid_point = (track.across_size - child_axis.across_size + child_axis.margin_across_start + - child_axis.margin_across_end) + / 2.0; + let end_point = track.across_size - child_axis.across_size - child_axis.margin_across_end; + let across_position = match child.layout_item { Some(LayoutItem { align_self: Some(align_self), .. }) => match align_self { - AlignSelf::Center => { - (track.across_size - child_axis.across_size + child_axis.margin_across_start - - child_axis.margin_across_end) - / 2.0 - } - AlignSelf::End => { - track.across_size - child_axis.across_size - child_axis.margin_across_end - } - _ => child_axis.margin_across_start, + AlignSelf::Center => mid_point, + AlignSelf::End => end_point, + _ => match layout_data.align_items { + AlignItems::Center => mid_point, + AlignItems::End => end_point, + _ => child_axis.margin_across_start, + }, }, _ => match layout_data.align_items { - AlignItems::Center => { - (track.across_size - child_axis.across_size + child_axis.margin_across_start - - child_axis.margin_across_end) - / 2.0 - } - AlignItems::End => { - track.across_size - child_axis.across_size - child_axis.margin_across_end - } + AlignItems::Center => mid_point, + AlignItems::End => end_point, _ => child_axis.margin_across_start, }, }; From 2901d0086209dff2312ab2e300c5fd98c3daafb8 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 21 Apr 2026 15:35:06 +0200 Subject: [PATCH 2/6] :bug: Fix errors in thumbnails --- frontend/src/app/render_wasm/api.cljs | 6 +++--- frontend/src/app/render_wasm/wasm.cljs | 3 +++ frontend/src/app/worker/thumbnails.cljs | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/render_wasm/api.cljs b/frontend/src/app/render_wasm/api.cljs index a68d3d1b24..897ea4aa55 100644 --- a/frontend/src/app/render_wasm/api.cljs +++ b/frontend/src/app/render_wasm/api.cljs @@ -334,7 +334,7 @@ (defn request-render [_requester] - (when (and wasm/context-initialized? (not @wasm/context-lost?)) + (when (and wasm/context-initialized? (not @wasm/context-lost?) (not @wasm/disable-request-render?)) (if @shapes-loading? (register-deferred-render!) (when-not @pending-render @@ -1698,6 +1698,8 @@ [] (when wasm/context-initialized? (try + (set! wasm/context-initialized? false) + ;; Cancel any pending animation frame to prevent race conditions (when wasm/internal-frame-id (js/cancelAnimationFrame wasm/internal-frame-id) @@ -1708,8 +1710,6 @@ (reset! shapes-loading? false) (reset! deferred-render? false) - ;; TODO: perform corresponding cleaning - (set! wasm/context-initialized? false) (h/call wasm/internal-module "_clean_up") ;; Remove event listener for WebGL context lost diff --git a/frontend/src/app/render_wasm/wasm.cljs b/frontend/src/app/render_wasm/wasm.cljs index 5c43ba4899..25c2908575 100644 --- a/frontend/src/app/render_wasm/wasm.cljs +++ b/frontend/src/app/render_wasm/wasm.cljs @@ -26,6 +26,9 @@ (defonce context-initialized? false) (defonce context-lost? (atom false)) +;; When we're rendering in a sync way we want to stop the asynchrous `request-render` +(defonce disable-request-render? (atom false)) + (defonce serializers #js {:blur-type shared/RawBlurType diff --git a/frontend/src/app/worker/thumbnails.cljs b/frontend/src/app/worker/thumbnails.cljs index d90536619d..d7692d63d4 100644 --- a/frontend/src/app/worker/thumbnails.cljs +++ b/frontend/src/app/worker/thumbnails.cljs @@ -170,6 +170,7 @@ (render/calculate-dimensions objects thumbnail-aspect-ratio)) zoom (/ width (:width vbox))] + (reset! wasm/disable-request-render? true) (wasm.api/initialize-viewport objects zoom vbox :background bgcolor From f9f3955503a7bb7dbd3036f5baacd80a57183c6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= Date: Tue, 21 Apr 2026 15:13:21 +0200 Subject: [PATCH 3/6] :bug: Fix trailing whitespace behavior in v2 editor --- .../main/ui/workspace/shapes/text/v2_editor.cljs | 12 +++++++++++- .../main/ui/workspace/shapes/text/v2_editor.scss | 15 +++++++++------ 2 files changed, 20 insertions(+), 7 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 525b1d0613..e81f841baf 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 @@ -367,6 +367,9 @@ (when (cf/check-browser? :safari) (mf/deref refs/selected-zoom)) + vbox + (mf/deref refs/vbox) + shape (cond-> shape (some? text-modifier) (dwt/apply-text-modifier text-modifier) @@ -385,13 +388,20 @@ selrect-width (:width selrect) max-width (max width selrect-width) max-height (max height selrect-height) + ;; During auto-width editing we keep the shape width trimmed, but the caret + ;; must be able to move after trailing spaces. Expand only the editor + ;; overlay up to one viewport width to avoid clipping caret rendering. + viewport-width (or (:width vbox) 0) + overlay-width (if (= (:grow-type shape) :auto-width) + (+ max-width viewport-width) + max-width) valign (-> shape :content :vertical-align) y (:y selrect) y (case valign "bottom" (+ y (- selrect-height height)) "center" (+ y (/ (- selrect-height height) 2)) y)] - [(assoc selrect :y y :width max-width :height max-height) transform]) + [(assoc selrect :y y :width overlay-width :height max-height) transform]) (let [bounds (gst/shape->rect shape) x (mth/min (dm/get-prop bounds :x) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.scss b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.scss index 642a4cabf3..d31fa44ef0 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.scss +++ b/frontend/src/app/main/ui/workspace/shapes/text/v2_editor.scss @@ -53,7 +53,10 @@ font-size: 0px; } - [data-itype="inline"] { + // Text spans emitted by @penpot/text-editor use `data-itype="span"`. + // Keep whitespace rules attached to the real node type so trailing spaces + // are handled consistently while editing. + [data-itype="span"] { box-sizing: content-box; display: inline; line-height: inherit; @@ -75,11 +78,11 @@ .grow-type-auto-width { [data-itype="span"], [data-itype="paragraph"] { - white-space: nowrap; - } - - [data-itype="span"] { - white-space-collapse: preserve; + // Keep auto-width editing on a single preformatted line so trailing + // spaces are part of caret geometry and browser selection math. + white-space: pre; + overflow-wrap: normal; + word-break: keep-all; } } From ca97a2840817d11ecae1bec332e62aa12571d566 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 22 Apr 2026 06:51:33 +0200 Subject: [PATCH 4/6] :bug: Avoid unnecesary repainting of frames when mouse hover --- .../app/main/ui/workspace/viewport_wasm.cljs | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs index 53062a9936..b95962c4c5 100644 --- a/frontend/src/app/main/ui/workspace/viewport_wasm.cljs +++ b/frontend/src/app/main/ui/workspace/viewport_wasm.cljs @@ -157,6 +157,10 @@ text-editor-ref (mf/use-ref nil) last-vern-ref (mf/use-ref nil) + ;; WASM grid overlay was visible last run (`hover-grid?` true). Used so `clear-grid` + ;; (expensive: `_hide_grid` + full `request-render`) runs only when hiding the overlay. + prev-hover-grid-shown?-ref (mf/use-ref false) + ;; STATE REFS disable-paste-ref (mf/use-ref false) in-viewport-ref (mf/use-ref false) @@ -253,9 +257,10 @@ (not page-transition?)) show-text-editor? (and editing-shape (= :text (:type editing-shape)) (not page-transition?)) - hover-grid? (and (some? @hover-top-frame-id) - (ctl/grid-layout? objects @hover-top-frame-id) - (not page-transition?)) + has-grid? (and (some? @hover-top-frame-id) + (ctl/grid-layout? objects @hover-top-frame-id)) + + hover-grid? (and has-grid? (not page-transition?)) show-grid-editor? (and editing-shape (ctl/grid-layout? editing-shape) (not page-transition?)) show-presence? (and page-id (not page-transition?)) @@ -425,11 +430,19 @@ (when (and @canvas-init? @initialized?) (wasm.api/set-canvas-background background))) - (mf/with-effect [@canvas-init? hover-grid? @hover-top-frame-id] + ;; Grid overlay: `clear-grid` must run only when the overlay was shown and is now off + ;; (e.g. leave grid frame, or `page-transition?`). Do not call it on every + ;; `hover-top-frame-id` change while not hovering a grid frame. + (mf/with-effect [@canvas-init? hover-grid?] (when @canvas-init? - (if hover-grid? - (wasm.api/show-grid @hover-top-frame-id) - (wasm.api/clear-grid)))) + (when (and (not hover-grid?) (mf/ref-val prev-hover-grid-shown?-ref)) + (wasm.api/clear-grid)) + (mf/set-ref-val! prev-hover-grid-shown?-ref hover-grid?))) + + (mf/with-effect [@canvas-init? has-grid? hover-grid? + (if (and has-grid? hover-grid?) @hover-top-frame-id ::no-grid-hover-id)] + (when (and @canvas-init? hover-grid?) + (wasm.api/show-grid @hover-top-frame-id))) (hooks/setup-dom-events zoom disable-paste-ref in-viewport-ref read-only? drawing-tool path-drawing?) (hooks/setup-viewport-size vport viewport-ref) From d549be33764f144055cb9a6b942b0547bfa4cfd0 Mon Sep 17 00:00:00 2001 From: Eva Marco Date: Wed, 22 Apr 2026 09:39:38 +0200 Subject: [PATCH 5/6] :recycle: Remove duplicated code (#9096) --- .../data/workspace/tokens/application.cljs | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/frontend/src/app/main/data/workspace/tokens/application.cljs b/frontend/src/app/main/data/workspace/tokens/application.cljs index 5f5b54e681..3ee7758284 100644 --- a/frontend/src/app/main/data/workspace/tokens/application.cljs +++ b/frontend/src/app/main/data/workspace/tokens/application.cljs @@ -607,46 +607,6 @@ :state state})] (apply rx/of (map #(%) actions))))))))) -(def attributes->shape-update - "Maps each attribute-set to the update function that applies it to a shape. - Used both here (to resolve the correct update fn when explicit attrs are - passed to toggle-token) and in propagation.cljs (re-exported from there)." - {ctt/border-radius-keys update-shape-radius-for-corners - ctt/color-keys update-fill-stroke - ctt/stroke-width-keys update-stroke-width - ctt/sizing-keys apply-dimensions-token - ctt/opacity-keys update-opacity - ctt/rotation-keys update-rotation - - ;; Typography - ctt/font-family-keys update-font-family - ctt/font-size-keys update-font-size - ctt/font-weight-keys update-font-weight - ctt/letter-spacing-keys update-letter-spacing - ctt/text-case-keys update-text-case - ctt/text-decoration-keys update-text-decoration - ctt/typography-token-keys update-typography - ctt/shadow-keys update-shadow - ctt/line-height-keys update-line-height - - ;; Layout - #{:x :y} update-shape-position - #{:p1 :p2 :p3 :p4} update-layout-padding - #{:m1 :m2 :m3 :m4} update-layout-item-margin - #{:column-gap :row-gap} update-layout-gap - #{:width :height} apply-dimensions-token - #{:layout-item-min-w :layout-item-min-h - :layout-item-max-w :layout-item-max-h} update-layout-sizing-limits}) - -;; Flattened per-individual-key version of attributes->shape-update. -;; Allows O(1) lookup of the update function for any single attribute. -(def ^:private attr->shape-update - (reduce - (fn [acc [attr-set update-fn]] - (into acc (map (fn [k] [k update-fn]) attr-set))) - {} - attributes->shape-update)) - ;; Events to apply / unapply tokens to shapes ------------------------------------------------------------ (def attributes->shape-update From f00ea8789f86e6494ea5d41b9fdfb3424079dcfe Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 22 Apr 2026 13:16:34 +0200 Subject: [PATCH 6/6] :paperclip: Update version on mcp module --- mcp/package.json | 2 +- mcp/scripts/set-version | 0 2 files changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 mcp/scripts/set-version diff --git a/mcp/package.json b/mcp/package.json index 6324fe3898..49c13b6cf8 100644 --- a/mcp/package.json +++ b/mcp/package.json @@ -1,6 +1,6 @@ { "name": "@penpot/mcp", - "version": "2.15.0-rc.1.153", + "version": "2.16.0-rc.1", "description": "MCP server for Penpot integration", "bin": { "penpot-mcp": "./bin/mcp-local.js" diff --git a/mcp/scripts/set-version b/mcp/scripts/set-version old mode 100644 new mode 100755