diff --git a/CHANGES.md b/CHANGES.md index cc459b19e6..9c9e0bf2c3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -24,6 +24,25 @@ - Fix text editor v1 focus [Taiga #13961](https://tree.taiga.io/project/penpot/issue/13961) +## 2.14.3 (Unreleased) + +### :sparkles: New features & Enhancements + +- Add webp export format to plugin types [Github #8870](https://github.com/penpot/penpot/pull/8870) + +### :bug: Bugs fixed + +- Fix component "broken" after switch variant [Taiga #12984](https://tree.taiga.io/project/penpot/issue/12984) +- Fix variants corner cases with selrect and points [Github #8882](https://github.com/penpot/penpot/pull/8882) +- Fix dashboard navigation tabs overlap with projects content when scrolling [Taiga #13962](https://tree.taiga.io/project/penpot/issue/13962) +- Fix text editor v1 focus [Taiga #13961](https://tree.taiga.io/project/penpot/issue/13961) +- Fix highlight on frames after rename [Github #8938](https://github.com/penpot/penpot/pull/8938) +- Fix TypeError in sd-token-uuid when resolving tokens interactively [Github #8929](https://github.com/penpot/penpot/pull/8929) +- Fix path drawing preview passing shape instead of content to next-node +- Fix swapped arguments in CLJS PathData `-nth` with default +- Normalize PathData coordinates to safe integer bounds on read + + ## 2.14.2 ### :sparkles: New features & Enhancements diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index 4de95715f8..7e703385e7 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -2016,14 +2016,17 @@ (defn- switch-fixed-layout-geom-change-value [prev-shape ; The shape before the switch current-shape ; The shape after the switch (a clean copy) + origin-shape ; The original shape attr] ;; When there is a layout with fixed h or v sizing, we need ;; to keep the width/height (and recalculate selrect and points) (let [prev-width (-> prev-shape :selrect :width) current-width (-> current-shape :selrect :width) + origin-width (-> origin-shape :selrect :width) prev-height (-> prev-shape :selrect :height) current-height (-> current-shape :selrect :height) + origin-height (-> origin-shape :selrect :height) x (-> current-shape :selrect :x) y (-> current-shape :selrect :y) @@ -2034,10 +2037,16 @@ final-width (if (= :fix h-sizing) current-width - prev-width) + (if (= origin-width current-width) + prev-width ;; same-size: preserve override + current-width)) ;; different-size: use new component's + final-height (if (= :fix v-sizing) current-height - prev-height) + (if (= origin-height current-height) + prev-height ;; same-size: preserve override + current-height)) ;; different-size: use new component's + selrect (assoc (:selrect current-shape) :width final-width :height final-height @@ -2066,6 +2075,25 @@ (or (:transform current-shape) (gmt/matrix))))))) +(defn- equal-geometry? + "Returns true when the value of `attr` in `shape` is considered equal + to the corresponding value in `origin-shape`, ignoring positional + displacement (x/y). + For :selrect we compare width/height only; + for :points we normalise each vector so the first point is the + origin before comparing." + [shape origin-shape attr] + (or (and (= attr :selrect) + (= (-> shape :selrect :width) (-> origin-shape :selrect :width)) + (= (-> shape :selrect :height) (-> origin-shape :selrect :height))) + (and (= attr :points) + (let [normalize-pts (fn [pts] + (when (seq pts) + (let [f (first pts)] + (mapv #(gpt/subtract % f) pts))))] + (= (normalize-pts (get shape :points)) + (normalize-pts (get origin-shape :points))))))) + (defn update-attrs-on-switch "Copy attributes that have changed in the shape previous to the switch @@ -2104,8 +2132,9 @@ ;; If the values are already equal, don't copy them (= (get previous-shape attr) (get current-shape attr)) - ;; If the value is the same as the origin, don't copy it - (= (get previous-shape attr) (get origin-ref-shape attr)) + ;; If :selrect/:points values are already equal ignoring displacement, + ;; don't copy them + (equal-geometry? previous-shape origin-ref-shape attr) ;; If the attr is not touched, don't copy it (not (touched sync-group)) @@ -2154,8 +2183,21 @@ skip-operations? (or skip-operations? ;; If we are going to reset the position data, skip the selrect attr - (and reset-pos-data? (= attr :selrect))) - + (and reset-pos-data? (= attr :selrect)) + ;; Avoid copying composite geometry attrs (:selrect/:points) when the + ;; variant dimensions differ but neither sizing is :fix. Without this, + ;; :width/:height are correctly skipped by the check above + ;; but :selrect/:points would still carry the old override dimensions, + ;; leaving the shape in an inconsistent state. When :fix sizing is + ;; present, switch-fixed-layout-geom-change-value handles the composite + ;; attrs and must NOT be bypassed. Path shapes are also handled + ;; separately via switch-path-change-value. + (and (contains? #{:selrect :points} attr) + (not path-change?) + (not (or (= :fix (:layout-item-h-sizing previous-shape)) + (= :fix (:layout-item-v-sizing previous-shape)))) + (or (not= (get origin-ref-shape :width) (get current-shape :width)) + (not= (get origin-ref-shape :height) (get current-shape :height))))) attr-val (when-not skip-operations? (cond @@ -2179,7 +2221,7 @@ (and (or (= :fix (:layout-item-h-sizing previous-shape)) (= :fix (:layout-item-v-sizing previous-shape))) (contains? #{:points :selrect :width :height} attr)) - (switch-fixed-layout-geom-change-value previous-shape current-shape attr) + (switch-fixed-layout-geom-change-value previous-shape current-shape origin-ref-shape attr) :else (get previous-shape attr))) diff --git a/common/src/app/common/types/path.cljc b/common/src/app/common/types/path.cljc index 757b9f1e95..f3b7c635ab 100644 --- a/common/src/app/common/types/path.cljc +++ b/common/src/app/common/types/path.cljc @@ -23,7 +23,7 @@ #?(:clj (set! *warn-on-reflection* true)) -(def ^:cosnt bool-group-style-properties bool/group-style-properties) +(def ^:const bool-group-style-properties bool/group-style-properties) (def ^:const bool-style-properties bool/style-properties) (defn get-default-bool-fills @@ -79,7 +79,7 @@ (defn close-subpaths "Given a content, searches a path for possible subpaths that can create closed loops and merge them; then return the transformed path - conten as PathData instance" + content as PathData instance" [content] (-> (subpath/close-subpaths content) (impl/from-plain))) diff --git a/common/src/app/common/types/path/impl.cljc b/common/src/app/common/types/path/impl.cljc index 2db3fcb2e9..bf3586fb0d 100644 --- a/common/src/app/common/types/path/impl.cljc +++ b/common/src/app/common/types/path/impl.cljc @@ -30,6 +30,18 @@ #?(:clj (set! *warn-on-reflection* true)) (def ^:const SEGMENT-U8-SIZE 28) + +(defn- normalize-coord + "Normalize a coordinate value to be within safe integer bounds. + Clamps values greater than max-safe-int to max-safe-int, + and values less than min-safe-int to min-safe-int. + Always returns a double." + [v] + (cond + (> v sm/max-safe-int) (double sm/max-safe-int) + (< v sm/min-safe-int) (double sm/min-safe-int) + :else (double v))) + (def ^:const SEGMENT-U32-SIZE (/ SEGMENT-U8-SIZE 4)) (defprotocol IPathData @@ -121,12 +133,12 @@ (if (< index size) (let [offset (* index SEGMENT-U8-SIZE) type (buf/read-short buffer offset) - c1x (buf/read-float buffer (+ offset 4)) - c1y (buf/read-float buffer (+ offset 8)) - c2x (buf/read-float buffer (+ offset 12)) - c2y (buf/read-float buffer (+ offset 16)) - x (buf/read-float buffer (+ offset 20)) - y (buf/read-float buffer (+ offset 24)) + c1x (normalize-coord (buf/read-float buffer (+ offset 4))) + c1y (normalize-coord (buf/read-float buffer (+ offset 8))) + c2x (normalize-coord (buf/read-float buffer (+ offset 12))) + c2y (normalize-coord (buf/read-float buffer (+ offset 16))) + x (normalize-coord (buf/read-float buffer (+ offset 20))) + y (normalize-coord (buf/read-float buffer (+ offset 24))) type (case type 1 :move-to 2 :line-to @@ -148,12 +160,12 @@ (if (< index size) (let [offset (* index SEGMENT-U8-SIZE) type (buf/read-short buffer offset) - c1x (buf/read-float buffer (+ offset 4)) - c1y (buf/read-float buffer (+ offset 8)) - c2x (buf/read-float buffer (+ offset 12)) - c2y (buf/read-float buffer (+ offset 16)) - x (buf/read-float buffer (+ offset 20)) - y (buf/read-float buffer (+ offset 24)) + c1x (normalize-coord (buf/read-float buffer (+ offset 4))) + c1y (normalize-coord (buf/read-float buffer (+ offset 8))) + c2x (normalize-coord (buf/read-float buffer (+ offset 12))) + c2y (normalize-coord (buf/read-float buffer (+ offset 16))) + x (normalize-coord (buf/read-float buffer (+ offset 20))) + y (normalize-coord (buf/read-float buffer (+ offset 24))) type (case type 1 :move-to 2 :line-to @@ -172,12 +184,12 @@ [buffer index f] (let [offset (* index SEGMENT-U8-SIZE) type (buf/read-short buffer offset) - c1x (buf/read-float buffer (+ offset 4)) - c1y (buf/read-float buffer (+ offset 8)) - c2x (buf/read-float buffer (+ offset 12)) - c2y (buf/read-float buffer (+ offset 16)) - x (buf/read-float buffer (+ offset 20)) - y (buf/read-float buffer (+ offset 24)) + c1x (normalize-coord (buf/read-float buffer (+ offset 4))) + c1y (normalize-coord (buf/read-float buffer (+ offset 8))) + c2x (normalize-coord (buf/read-float buffer (+ offset 12))) + c2y (normalize-coord (buf/read-float buffer (+ offset 16))) + x (normalize-coord (buf/read-float buffer (+ offset 20))) + y (normalize-coord (buf/read-float buffer (+ offset 24))) type (case type 1 :move-to 2 :line-to @@ -252,31 +264,31 @@ (let [offset (* index SEGMENT-U8-SIZE) type (buf/read-short buffer offset)] (case (long type) - 1 (let [x (buf/read-float buffer (+ offset 20)) - y (buf/read-float buffer (+ offset 24))] + 1 (let [x (normalize-coord (buf/read-float buffer (+ offset 20))) + y (normalize-coord (buf/read-float buffer (+ offset 24)))] {:command :move-to - :params {:x (double x) - :y (double y)}}) + :params {:x x + :y y}}) - 2 (let [x (buf/read-float buffer (+ offset 20)) - y (buf/read-float buffer (+ offset 24))] + 2 (let [x (normalize-coord (buf/read-float buffer (+ offset 20))) + y (normalize-coord (buf/read-float buffer (+ offset 24)))] {:command :line-to - :params {:x (double x) - :y (double y)}}) + :params {:x x + :y y}}) - 3 (let [c1x (buf/read-float buffer (+ offset 4)) - c1y (buf/read-float buffer (+ offset 8)) - c2x (buf/read-float buffer (+ offset 12)) - c2y (buf/read-float buffer (+ offset 16)) - x (buf/read-float buffer (+ offset 20)) - y (buf/read-float buffer (+ offset 24))] + 3 (let [c1x (normalize-coord (buf/read-float buffer (+ offset 4))) + c1y (normalize-coord (buf/read-float buffer (+ offset 8))) + c2x (normalize-coord (buf/read-float buffer (+ offset 12))) + c2y (normalize-coord (buf/read-float buffer (+ offset 16))) + x (normalize-coord (buf/read-float buffer (+ offset 20))) + y (normalize-coord (buf/read-float buffer (+ offset 24)))] {:command :curve-to - :params {:x (double x) - :y (double y) - :c1x (double c1x) - :c1y (double c1y) - :c2x (double c2x) - :c2y (double c2y)}}) + :params {:x x + :y y + :c1x c1x + :c1y c1y + :c2x c2x + :c2y c2y}}) 4 {:command :close-path :params {}} @@ -462,7 +474,7 @@ nil)) (-nth [_ i default] - (if (d/in-range? i size) + (if (d/in-range? size i) (read-segment buffer i) default)) @@ -666,8 +678,6 @@ (defn from-plain "Create a PathData instance from plain data structures" [segments] - (assert (check-plain-content segments)) - (let [total (count segments) buffer (buf/allocate (* total SEGMENT-U8-SIZE))] (loop [index 0] @@ -677,30 +687,28 @@ (case (get segment :command) :move-to (let [params (get segment :params) - x (float (get params :x)) - y (float (get params :y))] + x (normalize-coord (get params :x)) + y (normalize-coord (get params :y))] (buf/write-short buffer offset 1) (buf/write-float buffer (+ offset 20) x) (buf/write-float buffer (+ offset 24) y)) :line-to (let [params (get segment :params) - x (float (get params :x)) - y (float (get params :y))] - + x (normalize-coord (get params :x)) + y (normalize-coord (get params :y))] (buf/write-short buffer offset 2) (buf/write-float buffer (+ offset 20) x) (buf/write-float buffer (+ offset 24) y)) :curve-to (let [params (get segment :params) - x (float (get params :x)) - y (float (get params :y)) - c1x (float (get params :c1x x)) - c1y (float (get params :c1y y)) - c2x (float (get params :c2x x)) - c2y (float (get params :c2y y))] - + x (normalize-coord (get params :x)) + y (normalize-coord (get params :y)) + c1x (normalize-coord (get params :c1x x)) + c1y (normalize-coord (get params :c1y y)) + c2x (normalize-coord (get params :c2x x)) + c2y (normalize-coord (get params :c2y y))] (buf/write-short buffer offset 3) (buf/write-float buffer (+ offset 4) c1x) (buf/write-float buffer (+ offset 8) c1y) diff --git a/common/src/app/common/types/path/segment.cljc b/common/src/app/common/types/path/segment.cljc index 9eb36d7a12..bcbbe8eeda 100644 --- a/common/src/app/common/types/path/segment.cljc +++ b/common/src/app/common/types/path/segment.cljc @@ -62,7 +62,7 @@ (map (fn [[index _]] index)))) (defn handler-indices - "Return an index where the key is the positions and the values the handlers" + "Returns [[index prefix] ...] of all handlers associated with point." [content point] (->> (d/with-prev content) (d/enumerate) @@ -76,7 +76,7 @@ []))))) (defn opposite-index - "Calculates the opposite index given a prefix and an index" + "Calculates the opposite handler index given a content, index and prefix." [content index prefix] (let [point (if (= prefix :c2) diff --git a/common/src/app/common/types/path/shape_to_path.cljc b/common/src/app/common/types/path/shape_to_path.cljc index 8641ee556e..cc0f0c9060 100644 --- a/common/src/app/common/types/path/shape_to_path.cljc +++ b/common/src/app/common/types/path/shape_to_path.cljc @@ -32,7 +32,7 @@ (d/without-keys shape dissoc-attrs)) (defn- make-corner-arc - "Creates a curvle corner for border radius" + "Creates a curve corner for border radius" [from to corner radius] (let [x (case corner :top-left (:x from) diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index ee692aaffc..269848f8e4 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -489,17 +489,15 @@ (defn backtrace-tokens-tree "Convert tokens into a nested tree with their name as the path. - Generates a uuid per token to backtrace a token from an external source (StyleDictionary). + Uses the existing token :id to backtrace a token from an external source (StyleDictionary). The backtrace can't be the name as the name might not exist when the user is creating a token." [tokens] (reduce (fn [acc [_ token]] - (let [temp-id (random-uuid) - token (assoc token :temp/id temp-id) - path (get-token-path token)] + (let [path (get-token-path token)] (-> acc (assoc-in (concat [:tokens-tree] path) token) - (assoc-in [:ids temp-id] token)))) + (assoc-in [:ids (:id token)] token)))) {:tokens-tree {} :ids {}} tokens)) diff --git a/common/test/common_tests/logic/variants_switch_test.cljc b/common/test/common_tests/logic/variants_switch_test.cljc index d49764a389..f01da5f268 100644 --- a/common/test/common_tests/logic/variants_switch_test.cljc +++ b/common/test/common_tests/logic/variants_switch_test.cljc @@ -2257,4 +2257,469 @@ ;; or if it needs recalculation, the test validates the behavior (t/is (or (nil? old-position-data) (nil? new-position-data) - (not= old-position-data new-position-data))))) \ No newline at end of file + (not= old-position-data new-position-data))))) + +;; ============================================================ +;; SELRECT CONSISTENCY TESTS +;; These tests verify that after a variant switch, the composite +;; geometry attributes (:selrect, :points) stay consistent with +;; the scalar attributes (:width, :height) that are kept. +;; ============================================================ + +(t/deftest test-switch-selrect-consistent-no-sizing-different-widths + ;; When no :fix sizing and variants have different widths, + ;; :width is correctly skipped (stays at new component width), + ;; but :selrect was being copied from the old shape, leaving + ;; selrect.width inconsistent with :width. This test verifies the fix. + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (thv/add-variant-with-child + :v01 :c01 :m01 :c02 :m02 :r01 :r02 + {:child1-params {:width 100 :height 50} + :child2-params {:width 200 :height 50}}) + + (thc/instantiate-component :c01 + :copy01 + :children-labels [:copy-r01])) + + page (thf/current-page file) + copy01 (ths/get-shape file :copy01) + rect01 (get-in page [:objects (-> copy01 :shapes first)]) + + ;; Override width AND selrect consistently (simulating a real resize) + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id rect01)} + (fn [shape] + (let [new-width 150 + sr (:selrect shape) + new-sr (-> sr + (assoc :width new-width) + (assoc :x2 (+ (:x1 sr) new-width)))] + (-> shape + (assoc :width new-width) + (assoc :selrect new-sr)))) + (:objects page) + {}) + + file (thf/apply-changes file changes) + page (thf/current-page file) + rect01 (get-in page [:objects (:id rect01)]) + + ;; ==== Action + file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) + + page' (thf/current-page file') + copy02' (ths/get-shape file' :copy02) + rect02' (get-in page' [:objects (-> copy02' :shapes first)])] + + ;; The rect had the width override before the switch + (t/is (= (:width rect01) 150)) + (t/is (= (get-in rect01 [:selrect :width]) 150)) + ;; Since the variants have different widths (100 vs 200), the override is not preserved + (t/is (= (:width rect02') 200)) + ;; The selrect must be consistent with :width + (t/is (= (get-in rect02' [:selrect :width]) 200)))) + +(t/deftest test-switch-selrect-consistent-no-sizing-different-heights + ;; Same as above but for height. + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (thv/add-variant-with-child + :v01 :c01 :m01 :c02 :m02 :r01 :r02 + {:child1-params {:width 50 :height 100} + :child2-params {:width 50 :height 200}}) + + (thc/instantiate-component :c01 + :copy01 + :children-labels [:copy-r01])) + + page (thf/current-page file) + copy01 (ths/get-shape file :copy01) + rect01 (get-in page [:objects (-> copy01 :shapes first)]) + + ;; Override height AND selrect consistently + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id rect01)} + (fn [shape] + (let [new-height 150 + sr (:selrect shape) + new-sr (-> sr + (assoc :height new-height) + (assoc :y2 (+ (:y1 sr) new-height)))] + (-> shape + (assoc :height new-height) + (assoc :selrect new-sr)))) + (:objects page) + {}) + + file (thf/apply-changes file changes) + page (thf/current-page file) + rect01 (get-in page [:objects (:id rect01)]) + + ;; ==== Action + file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) + + page' (thf/current-page file') + copy02' (ths/get-shape file' :copy02) + rect02' (get-in page' [:objects (-> copy02' :shapes first)])] + + ;; The rect had the height override before the switch + (t/is (= (:height rect01) 150)) + (t/is (= (get-in rect01 [:selrect :height]) 150)) + ;; Since the variants have different heights (100 vs 200), the override is not preserved + (t/is (= (:height rect02') 200)) + ;; The selrect must be consistent with :height + (t/is (= (get-in rect02' [:selrect :height]) 200)))) + +(t/deftest test-switch-with-v-sizing-fix-selrect-consistent-different-widths + ;; mixed-sizing scenario: v-sizing=:fix but variants differ in WIDTH. + ;; switch-fixed-layout-geom-change-value is triggered (because v-sizing=:fix). + ;; Without the fix, the function returned prev-width for the non-:fix dimension, + ;; leaving selrect.width inconsistent with :width. + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (thv/add-variant-with-child + :v01 :c01 :m01 :c02 :m02 :r01 :r02 + {:child1-params {:width 100 :height 50 :layout-item-v-sizing :fix} + :child2-params {:width 200 :height 50 :layout-item-v-sizing :fix}}) + + (thc/instantiate-component :c01 + :copy01 + :children-labels [:copy-r01])) + + page (thf/current-page file) + copy01 (ths/get-shape file :copy01) + rect01 (get-in page [:objects (-> copy01 :shapes first)]) + + ;; Override width AND selrect consistently + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id rect01)} + (fn [shape] + (let [new-width 150 + sr (:selrect shape) + new-sr (-> sr + (assoc :width new-width) + (assoc :x2 (+ (:x1 sr) new-width)))] + (-> shape + (assoc :width new-width) + (assoc :selrect new-sr)))) + (:objects page) + {}) + + file (thf/apply-changes file changes) + page (thf/current-page file) + rect01 (get-in page [:objects (:id rect01)]) + + ;; ==== Action + file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) + + page' (thf/current-page file') + copy02' (ths/get-shape file' :copy02) + rect02' (get-in page' [:objects (-> copy02' :shapes first)])] + + ;; The rect had the width override before the switch + (t/is (= (:width rect01) 150)) + (t/is (= (get-in rect01 [:selrect :width]) 150)) + ;; Since the variants have different widths (100 vs 200), the override is not preserved + ;; (v-sizing=:fix does not affect the horizontal dimension) + (t/is (= (:width rect02') 200)) + ;; The selrect must be consistent with :width + (t/is (= (get-in rect02' [:selrect :width]) 200)) + ;; v-sizing is preserved + (t/is (= (:layout-item-v-sizing rect02') :fix)))) + +(t/deftest test-switch-with-h-sizing-fix-selrect-consistent-different-heights + ;; mixed-sizing scenario: h-sizing=:fix but variants differ in HEIGHT. + ;; switch-fixed-layout-geom-change-value is triggered (because h-sizing=:fix). + ;; Without the fix, the function returned prev-height for the non-:fix dimension, + ;; leaving selrect.height inconsistent with :height. + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (thv/add-variant-with-child + :v01 :c01 :m01 :c02 :m02 :r01 :r02 + {:child1-params {:width 50 :height 100 :layout-item-h-sizing :fix} + :child2-params {:width 50 :height 200 :layout-item-h-sizing :fix}}) + + (thc/instantiate-component :c01 + :copy01 + :children-labels [:copy-r01])) + + page (thf/current-page file) + copy01 (ths/get-shape file :copy01) + rect01 (get-in page [:objects (-> copy01 :shapes first)]) + + ;; Override height AND selrect consistently + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id rect01)} + (fn [shape] + (let [new-height 150 + sr (:selrect shape) + new-sr (-> sr + (assoc :height new-height) + (assoc :y2 (+ (:y1 sr) new-height)))] + (-> shape + (assoc :height new-height) + (assoc :selrect new-sr)))) + (:objects page) + {}) + + file (thf/apply-changes file changes) + page (thf/current-page file) + rect01 (get-in page [:objects (:id rect01)]) + + ;; ==== Action + file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) + + page' (thf/current-page file') + copy02' (ths/get-shape file' :copy02) + rect02' (get-in page' [:objects (-> copy02' :shapes first)])] + + ;; The rect had the height override before the switch + (t/is (= (:height rect01) 150)) + (t/is (= (get-in rect01 [:selrect :height]) 150)) + ;; Since the variants have different heights (100 vs 200), the override is not preserved + ;; (h-sizing=:fix does not affect the vertical dimension) + (t/is (= (:height rect02') 200)) + ;; The selrect must be consistent with :height + (t/is (= (get-in rect02' [:selrect :height]) 200)) + ;; h-sizing is preserved + (t/is (= (:layout-item-h-sizing rect02') :fix)))) + +;; ============================================================ +;; FIXED-SIZING: "SAME-SIZE → PRESERVE OVERRIDE" PATH TESTS +;; These tests exercise the branch inside switch-fixed-layout-geom-change-value +;; where variants share the same value in the non-:fix dimension: +;; (if (= origin-dim current-dim) prev-dim current-dim) +;; When origin-dim == current-dim the user's override for that dimension +;; must be preserved after the switch. +;; ============================================================ + +(t/deftest test-switch-with-h-sizing-fix-same-height-override-preserved + ;; h-sizing=:fix, variants have SAME height (non-:fix dim, same-size). + ;; switch-fixed-layout-geom-change-value must return prev-height for the + ;; non-:fix dimension because origin-height == current-height. + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (thv/add-variant-with-child + :v01 :c01 :m01 :c02 :m02 :r01 :r02 + {:child1-params {:width 100 :height 50 :layout-item-h-sizing :fix} + :child2-params {:width 200 :height 50 :layout-item-h-sizing :fix}}) + (thc/instantiate-component :c01 + :copy01 + :children-labels [:copy-r01])) + + page (thf/current-page file) + copy01 (ths/get-shape file :copy01) + rect01 (get-in page [:objects (-> copy01 :shapes first)]) + + ;; Override height (the non-:fix dimension) and selrect consistently + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id rect01)} + (fn [shape] + (let [new-height 75 + sr (:selrect shape) + new-sr (-> sr + (assoc :height new-height) + (assoc :y2 (+ (:y1 sr) new-height)))] + (-> shape + (assoc :height new-height) + (assoc :selrect new-sr)))) + (:objects page) + {}) + + file (thf/apply-changes file changes) + page (thf/current-page file) + rect01 (get-in page [:objects (:id rect01)]) + + ;; ==== Action + file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) + + page' (thf/current-page file') + copy02' (ths/get-shape file' :copy02) + rect02' (get-in page' [:objects (-> copy02' :shapes first)])] + + ;; The rect had the height override 75 before the switch + (t/is (= (:height rect01) 75)) + ;; h-sizing=:fix means width always takes the new component's value + (t/is (= (:width rect02') 200)) + ;; Height (non-:fix dim) is preserved because both variants have same height (50) + (t/is (= (:height rect02') 75)) + ;; selrect must be consistent with the preserved height + (t/is (= (get-in rect02' [:selrect :height]) 75)) + (t/is (= (get-in rect02' [:selrect :width]) 200)) + ;; h-sizing is preserved + (t/is (= (:layout-item-h-sizing rect02') :fix)))) + +(t/deftest test-switch-with-v-sizing-fix-same-width-override-preserved + ;; v-sizing=:fix, variants have SAME width (non-:fix dim, same-size). + ;; switch-fixed-layout-geom-change-value must return prev-width for the + ;; non-:fix dimension because origin-width == current-width. + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (thv/add-variant-with-child + :v01 :c01 :m01 :c02 :m02 :r01 :r02 + {:child1-params {:width 100 :height 50 :layout-item-v-sizing :fix} + :child2-params {:width 100 :height 100 :layout-item-v-sizing :fix}}) + (thc/instantiate-component :c01 + :copy01 + :children-labels [:copy-r01])) + + page (thf/current-page file) + copy01 (ths/get-shape file :copy01) + rect01 (get-in page [:objects (-> copy01 :shapes first)]) + + ;; Override width (the non-:fix dimension) and selrect consistently + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id rect01)} + (fn [shape] + (let [new-width 150 + sr (:selrect shape) + new-sr (-> sr + (assoc :width new-width) + (assoc :x2 (+ (:x1 sr) new-width)))] + (-> shape + (assoc :width new-width) + (assoc :selrect new-sr)))) + (:objects page) + {}) + + file (thf/apply-changes file changes) + page (thf/current-page file) + rect01 (get-in page [:objects (:id rect01)]) + + ;; ==== Action + file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) + + page' (thf/current-page file') + copy02' (ths/get-shape file' :copy02) + rect02' (get-in page' [:objects (-> copy02' :shapes first)])] + + ;; The rect had the width override 150 before the switch + (t/is (= (:width rect01) 150)) + ;; Width (non-:fix dim) is preserved because both variants have same width (100) + (t/is (= (:width rect02') 150)) + ;; selrect must be consistent with the preserved width + (t/is (= (get-in rect02' [:selrect :width]) 150)) + ;; v-sizing=:fix means height always takes the new component's value + (t/is (= (:height rect02') 100)) + (t/is (= (get-in rect02' [:selrect :height]) 100)) + ;; v-sizing is preserved + (t/is (= (:layout-item-v-sizing rect02') :fix)))) + +(t/deftest test-switch-with-both-sizing-fix-overrides-discarded + ;; When both h-sizing=:fix and v-sizing=:fix, switch-fixed-layout-geom-change-value + ;; always uses current-width and current-height (the new component's values). + ;; Both width and height overrides are discarded because :fix always + ;; defers to the new component's dimension regardless of same-size or not. + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (thv/add-variant-with-child + :v01 :c01 :m01 :c02 :m02 :r01 :r02 + {:child1-params {:width 100 :height 50 + :layout-item-h-sizing :fix + :layout-item-v-sizing :fix} + :child2-params {:width 200 :height 100 + :layout-item-h-sizing :fix + :layout-item-v-sizing :fix}}) + (thc/instantiate-component :c01 + :copy01 + :children-labels [:copy-r01])) + + page (thf/current-page file) + copy01 (ths/get-shape file :copy01) + rect01 (get-in page [:objects (-> copy01 :shapes first)]) + + ;; Override both width and height (and selrect) consistently + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id rect01)} + (fn [shape] + (let [new-width 150 + new-height 75 + sr (:selrect shape) + new-sr (-> sr + (assoc :width new-width) + (assoc :height new-height) + (assoc :x2 (+ (:x1 sr) new-width)) + (assoc :y2 (+ (:y1 sr) new-height)))] + (-> shape + (assoc :width new-width) + (assoc :height new-height) + (assoc :selrect new-sr)))) + (:objects page) + {}) + + file (thf/apply-changes file changes) + page (thf/current-page file) + rect01 (get-in page [:objects (:id rect01)]) + + ;; ==== Action + file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) + + page' (thf/current-page file') + copy02' (ths/get-shape file' :copy02) + rect02' (get-in page' [:objects (-> copy02' :shapes first)])] + + ;; The rect had both overrides before the switch + (t/is (= (:width rect01) 150)) + (t/is (= (:height rect01) 75)) + ;; With both sizing :fix, both dimensions take the new component's values + (t/is (= (:width rect02') 200)) + (t/is (= (:height rect02') 100)) + ;; selrect must be consistent + (t/is (= (get-in rect02' [:selrect :width]) 200)) + (t/is (= (get-in rect02' [:selrect :height]) 100)) + (t/is (= (:layout-item-h-sizing rect02') :fix)) + (t/is (= (:layout-item-v-sizing rect02') :fix)))) + +(t/deftest test-switch-same-size-variants-geometry-override-preserved + ;; When both variants have IDENTICAL dimensions (width=100, height=50), + ;; the guard that skips :selrect/:points must NOT fire + ;; (its condition `(or (not= origin.width current.width) ...)` is false). + ;; A geometry override should therefore be carried through correctly. + (let [;; ==== Setup + file (-> (thf/sample-file :file1) + (thv/add-variant-with-child + :v01 :c01 :m01 :c02 :m02 :r01 :r02 + {:child1-params {:width 100 :height 50} + :child2-params {:width 100 :height 50}}) ; same size! + (thc/instantiate-component :c01 + :copy01 + :children-labels [:copy-r01])) + + page (thf/current-page file) + copy01 (ths/get-shape file :copy01) + rect01 (get-in page [:objects (-> copy01 :shapes first)]) + + ;; Override width AND selrect consistently (simulating a real resize) + changes (cls/generate-update-shapes (pcb/empty-changes nil (:id page)) + #{(:id rect01)} + (fn [shape] + (let [new-width 150 + sr (:selrect shape) + new-sr (-> sr + (assoc :width new-width) + (assoc :x2 (+ (:x1 sr) new-width)))] + (-> shape + (assoc :width new-width) + (assoc :selrect new-sr)))) + (:objects page) + {}) + + file (thf/apply-changes file changes) + page (thf/current-page file) + rect01 (get-in page [:objects (:id rect01)]) + + ;; ==== Action + file' (tho/swap-component file copy01 :c02 {:new-shape-label :copy02 :keep-touched? true}) + + page' (thf/current-page file') + copy02' (ths/get-shape file' :copy02) + rect02' (get-in page' [:objects (-> copy02' :shapes first)])] + + ;; The rect had the width override 150 before the switch + (t/is (= (:width rect01) 150)) + (t/is (= (get-in rect01 [:selrect :width]) 150)) + ;; Both variants are identical in size (100x50), so the override IS preserved + (t/is (= (:width rect02') 150)) + ;; The guard must not have suppressed :selrect — it should be consistent + (t/is (= (get-in rect02' [:selrect :width]) 150)))) \ No newline at end of file diff --git a/common/test/common_tests/types/path_data_test.cljc b/common/test/common_tests/types/path_data_test.cljc index 252334b459..e4d2881b18 100644 --- a/common/test/common_tests/types/path_data_test.cljc +++ b/common/test/common_tests/types/path_data_test.cljc @@ -13,6 +13,7 @@ [app.common.geom.rect :as grc] [app.common.math :as mth] [app.common.pprint :as pp] + [app.common.schema :as sm] [app.common.transit :as trans] [app.common.types.path :as path] [app.common.types.path.bool :as path.bool] @@ -1418,3 +1419,60 @@ ;; Verify first and last entries specifically (t/is (= :move-to (first seq-types))) (t/is (= :close-path (last seq-types)))))) + +(t/deftest path-data-read-normalizes-out-of-bounds-coordinates + (let [max-safe (double sm/max-safe-int) + min-safe (double sm/min-safe-int) + ;; Create content with values exceeding safe bounds + content-with-out-of-bounds + [{:command :move-to :params {:x (+ max-safe 1000.0) :y (- min-safe 1000.0)}} + {:command :line-to :params {:x (- min-safe 500.0) :y (+ max-safe 500.0)}} + {:command :curve-to :params + {:c1x (+ max-safe 200.0) :c1y (- min-safe 200.0) + :c2x (+ max-safe 300.0) :c2y (- min-safe 300.0) + :x (+ max-safe 400.0) :y (- min-safe 400.0)}} + {:command :close-path :params {}}] + + ;; Create PathData from the content + pdata (path/content content-with-out-of-bounds) + + ;; Read it back + result (vec pdata)] + + (t/testing "Coordinates exceeding max-safe-int are clamped to max-safe-int" + (let [move-to (first result) + line-to (second result)] + (t/is (= max-safe (:x (:params move-to))) "x in move-to should be clamped to max-safe-int") + (t/is (= min-safe (:y (:params move-to))) "y in move-to should be clamped to min-safe-int") + (t/is (= min-safe (:x (:params line-to))) "x in line-to should be clamped to min-safe-int") + (t/is (= max-safe (:y (:params line-to))) "y in line-to should be clamped to max-safe-int"))) + + (t/testing "Curve-to coordinates are clamped" + (let [curve-to (nth result 2)] + (t/is (= max-safe (:c1x (:params curve-to))) "c1x should be clamped") + (t/is (= min-safe (:c1y (:params curve-to))) "c1y should be clamped") + (t/is (= max-safe (:c2x (:params curve-to))) "c2x should be clamped") + (t/is (= min-safe (:c2y (:params curve-to))) "c2y should be clamped") + (t/is (= max-safe (:x (:params curve-to))) "x should be clamped") + (t/is (= min-safe (:y (:params curve-to))) "y should be clamped"))) + + (t/testing "-lookup normalizes coordinates" + (let [move-to (path.impl/-lookup pdata 0 (fn [_ _ _ _ _ x y] {:x x :y y}))] + (t/is (= max-safe (:x move-to)) "lookup x should be clamped") + (t/is (= min-safe (:y move-to)) "lookup y should be clamped"))) + + (t/testing "-walk normalizes coordinates" + (let [coords (path.impl/-walk pdata + (fn [_ _ _ _ _ x y] + (when (and x y) {:x x :y y})) + [])] + (t/is (= max-safe (:x (first coords))) "walk first x should be clamped") + (t/is (= min-safe (:y (first coords))) "walk first y should be clamped"))) + + (t/testing "-reduce normalizes coordinates" + (let [[move-res] (path.impl/-reduce pdata + (fn [acc _ _ _ _ _ _ x y] + (if (and x y) (conj acc {:x x :y y}) acc)) + [])] + (t/is (= max-safe (:x move-res)) "reduce first x should be clamped") + (t/is (= min-safe (:y move-res)) "reduce first y should be clamped"))))) diff --git a/frontend/src/app/main/data/style_dictionary.cljs b/frontend/src/app/main/data/style_dictionary.cljs index 63a076f93f..48320fd9a0 100644 --- a/frontend/src/app/main/data/style_dictionary.cljs +++ b/frontend/src/app/main/data/style_dictionary.cljs @@ -551,7 +551,7 @@ (.. sd-token -original -name)) (defn sd-token-uuid [^js sd-token] - (uuid (.-uuid (.-id ^js sd-token)))) + (uuid (.-uuid (.. sd-token -original -id)))) (defn resolve-tokens [tokens] @@ -560,15 +560,23 @@ (defn resolve-tokens-interactive "Interactive check of resolving tokens. - Uses a ids map to backtrace the original token from the resolved StyleDictionary token. + Uses a ids map to backtrace the original token from the resolved + StyleDictionary token. - We have to pass in all tokens from all sets in the entire library to style dictionary - so we know if references are missing / to resolve them and possibly show interactive previews (in the tokens form) to the user. + We have to pass in all tokens from all sets in the entire library to + style dictionary so we know if references are missing / to resolve + them and possibly show interactive previews (in the tokens form) to + the user. - Since we're using the :name path as the identifier we might be throwing away or overriding tokens in the tree that we pass to StyleDictionary. + Since we're using the :name path as the identifier we might be + throwing away or overriding tokens in the tree that we pass to + StyleDictionary. - So to get back the original token from the resolved sd-token (see my updates for what an sd-token is) we include a temporary :id for the token that we pass to StyleDictionary, - this way after the resolving computation we can restore any token, even clashing ones with the same :name path by just looking up that :id in the ids map." + So to get back the original token from the resolved sd-token (see my + updates for what an sd-token is) we include a temporary :id for the + token that we pass to StyleDictionary, this way after the resolving + computation we can restore any token, even clashing ones with the + same :name path by just looking up that :id in the ids map." [tokens] (let [{:keys [tokens-tree ids]} (ctob/backtrace-tokens-tree tokens)] (resolve-tokens-tree tokens-tree #(get ids (sd-token-uuid %))))) @@ -584,10 +592,11 @@ (defonce !tokens-cache (atom nil)) (defn use-resolved-tokens - "The StyleDictionary process function is async, so we can't use resolved values directly. + "The StyleDictionary process function is async, so we can't use + resolved values directly. - This hook will return the unresolved tokens as state until they are processed, - then the state will be updated with the resolved tokens." + This hook will return the unresolved tokens as state until they are + processed, then the state will be updated with the resolved tokens." [tokens & {:keys [cache-atom interactive?] :or {cache-atom !tokens-cache} :as config}] diff --git a/frontend/test/frontend_tests/tokens/style_dictionary_test.cljs b/frontend/test/frontend_tests/tokens/style_dictionary_test.cljs index bf8aad0c35..1f1609f344 100644 --- a/frontend/test/frontend_tests/tokens/style_dictionary_test.cljs +++ b/frontend/test/frontend_tests/tokens/style_dictionary_test.cljs @@ -57,3 +57,29 @@ (t/is (= :error.token/number-too-large (get-in resolved-tokens ["borderRadius.largeFn" :errors 0 :error/code]))) (done)))))))) + +(t/deftest resolve-tokens-interactive-test + (t/async + done + (t/testing "resolves tokens interactively using backtrace ids map" + (let [tokens (-> (ctob/make-tokens-lib) + (ctob/add-set (ctob/make-token-set :id (cthi/new-id! :core-set) + :name "core")) + (ctob/add-token (cthi/id :core-set) + (ctob/make-token {:name "borderRadius.sm" + :value "12px" + :type :border-radius})) + (ctob/add-token (cthi/id :core-set) + (ctob/make-token {:value "{borderRadius.sm} * 2" + :name "borderRadius.md" + :type :border-radius})) + (ctob/get-all-tokens-map))] + (-> (sd/resolve-tokens-interactive tokens) + (rx/sub! + (fn [resolved-tokens] + (t/is (= 12 (get-in resolved-tokens ["borderRadius.sm" :resolved-value]))) + (t/is (= "px" (get-in resolved-tokens ["borderRadius.sm" :unit]))) + (t/is (= 24 (get-in resolved-tokens ["borderRadius.md" :resolved-value]))) + (t/is (= "px" (get-in resolved-tokens ["borderRadius.md" :unit]))) + (done)))))))) + diff --git a/package.json b/package.json index c0351b106d..26baa0d989 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,6 @@ "@github/copilot": "^1.0.21", "@types/node": "^25.5.2", "esbuild": "^0.28.0", - "opencode-ai": "^1.4.0" + "opencode-ai": "^1.4.3" } } diff --git a/plugins/CHANGELOG.md b/plugins/CHANGELOG.md index 203e5e0e81..4b58244e86 100644 --- a/plugins/CHANGELOG.md +++ b/plugins/CHANGELOG.md @@ -1,12 +1,13 @@ ## 1.5.0 (Unreleased) -- **plugin-types**: Added a flags subcontexts with the flag `naturalChildrenOrdering` - **plugins-runtime**: Added `version` field that returns the current version +- **plugin-types**: Added a flags subcontexts with the flag `naturalChildrenOrdering` - **plugin-types**: Fix penpot.openPage() to navigate in same tab by default - **plugin-types**: Added `createVariantFromComponents` - **plugin-types**: Change return type of `combineAsVariants` - **plugin-types**: Added `textBounds` property for text shapes - **plugin-types**: Added flag `throwValidationErrors` to enable exceptions on validation +- **plugin-types**: Fix missing `webp` export format in `Export.type` ## 1.4.2 (2026-01-21) diff --git a/plugins/libs/plugin-types/index.d.ts b/plugins/libs/plugin-types/index.d.ts index 1ebface05c..dd42f365fe 100644 --- a/plugins/libs/plugin-types/index.d.ts +++ b/plugins/libs/plugin-types/index.d.ts @@ -1548,9 +1548,9 @@ export interface EventsMap { */ export interface Export { /** - * Type of the file to export. Can be one of the following values: png, jpeg, svg, pdf + * Type of the file to export. Can be one of the following values: png, jpeg, webp, svg, pdf */ - type: 'png' | 'jpeg' | 'svg' | 'pdf'; + type: 'png' | 'jpeg' | 'webp' | 'svg' | 'pdf'; /** * For bitmap formats represent the scale of the original size to resize the export */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d06094e10f..276b2891e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: ^0.28.0 version: 0.28.0 opencode-ai: - specifier: ^1.4.0 - version: 1.4.0 + specifier: ^1.4.3 + version: 1.4.3 packages: @@ -227,67 +227,67 @@ packages: engines: {node: '>=18'} hasBin: true - opencode-ai@1.4.0: - resolution: {integrity: sha512-Cb5Vo5Rl1gvOIXC8gtgupwoa5+rufsp+6u5tIxIYLl5fReX+P2eugLSOkKH2KB5GC6BwxaEvapEZiPvQYsZSXA==} + opencode-ai@1.4.3: + resolution: {integrity: sha512-WwCSrLgJiS+sLIWoi9pa62vAw3l6VI3a+ShhjDDMUJBBG2FxU18xEhk8xhEedLMKyHo1p0nwD41+iKZ1y+rdAw==} hasBin: true - opencode-darwin-arm64@1.4.0: - resolution: {integrity: sha512-rXdrH1Oejb+220ZCzkd1P+tCP7IhLTyfRbUr89vzvEWVRueh0vr2hvyrGDVv9LAskZAt/hwY3Wnw9CzjtxocdQ==} + opencode-darwin-arm64@1.4.3: + resolution: {integrity: sha512-d/MT28Is5yhdFY+36AqKc5r31zx8lXTQIYblfn5R8kdhamXijZVGdD0pHl3eJc1ZolUHNwzg2B+IqV22uyU9GQ==} cpu: [arm64] os: [darwin] - opencode-darwin-x64-baseline@1.4.0: - resolution: {integrity: sha512-5xCXF8Xn9M2WQKZATc4llm9qrAc4JodmQj88Oibbz/rXIOr/A1ejXvaeqLOQkQyQweeEABlYXOf3eCiY5hx8Gw==} + opencode-darwin-x64-baseline@1.4.3: + resolution: {integrity: sha512-WTqf7WBNRZcv6pClqnN4F7X/T/osgcPGikNHkHUSLszKWg9flqz7Z68kHR4i9ae8Bn3ke9MQRgzRdOt2PgLL0w==} cpu: [x64] os: [darwin] - opencode-darwin-x64@1.4.0: - resolution: {integrity: sha512-PhBfT2EtPos7jcGBtVSz3+yKv2e1nQy1UrXiH4ILdSgwzroKU/0kMsRtWJeMPHIj1imUQmSVlnDcuxiCiCkozw==} + opencode-darwin-x64@1.4.3: + resolution: {integrity: sha512-8FUHeybVmaCYt4S2YmWcf32o/xa/ahCfI258bpWssrzs7Xg51JgUB/Csoble0I1mH7RpW39SKy/hHUtHGuJfJg==} cpu: [x64] os: [darwin] - opencode-linux-arm64-musl@1.4.0: - resolution: {integrity: sha512-1lc0Nj6OmtJF5NJn+AhV7rXOHzw+0p7rvXQu2iqd9V7DpUEbltyF6oiyuF54gBZjHpvSzFXu8a3YeTcuMEXdNA==} + opencode-linux-arm64-musl@1.4.3: + resolution: {integrity: sha512-3Ej2klaep8+fxcc44UyEuRpb/UFiNkwfzIDLIST83hFUtjzprjpTRqg6zHmOfzyfjNAaNpB4VZw6e9y3mGBpiQ==} cpu: [arm64] os: [linux] - opencode-linux-arm64@1.4.0: - resolution: {integrity: sha512-XEM3tP7DTrYDEYCe9jqC/xtgzPJpCZTfinc5DjcPuh2hx+iHCGSr9+qG7tRGeCyvD9ibAFewNtpco5Is49JCrg==} + opencode-linux-arm64@1.4.3: + resolution: {integrity: sha512-9jpVSOEF7TX3gPPAHVAsBT9XEO3LgYafI+IUmOzbBB9CDiVVNJw6JmEffmSpSxY4nkAh322xnMbNjVGEyXQBRA==} cpu: [arm64] os: [linux] - opencode-linux-x64-baseline-musl@1.4.0: - resolution: {integrity: sha512-URg1JXIUaNz0R4TLUT98rK2jozmh5ScAkkqxPK6LWj3XwJojJx23mJRNgLb26034PgNkUwXhrtdbnyPTSVlkqQ==} + opencode-linux-x64-baseline-musl@1.4.3: + resolution: {integrity: sha512-aned/3FQTHXXQv2PPKDprJwQaQkoadriQ6AByGhRl6/bHhSkhkiVl6cHHvYMKxYEwN4bVOydWhasfgm/xru/xw==} cpu: [x64] os: [linux] - opencode-linux-x64-baseline@1.4.0: - resolution: {integrity: sha512-GocjLGNgs41PLgSVPWxT3Do0StZkDB9QF3e3VIIAGzPmOVcpTZLdDvJPkZdRbRGcVfUcSRGquBbBgvwK9Zsw4w==} + opencode-linux-x64-baseline@1.4.3: + resolution: {integrity: sha512-HpzdgYaI90qqt0WokcyBhadgFQ0EYMhq4TZ4EcaSPuZTssS2Drb6kp70Si54uOJL/MUAdc9+E0BYYIAdOJ6h1g==} cpu: [x64] os: [linux] - opencode-linux-x64-musl@1.4.0: - resolution: {integrity: sha512-yGb1uNO++BtkZ7X/LGLax9ppaEvsmn5s5GaAqcrYj/SyJA5cL2IYzEeMYRAsrb0b81fQCSq5SLEiWiMq1o59oA==} + opencode-linux-x64-musl@1.4.3: + resolution: {integrity: sha512-ibUevyDxVrwkp6FWu8UBCBsrzlKDT/uEug2NHCKaHIwo9uwVf5zsL/0ueHYqmH14SHK+M6wzWewYk6WuW9f0zQ==} cpu: [x64] os: [linux] - opencode-linux-x64@1.4.0: - resolution: {integrity: sha512-Ops08slOBhHbKaYhERH8zMTjlM6mearVaA0udCDIx2fGqDbZRisoRyyI6Z44GPYBH02w8eGmvOvnF5fQYyq2fw==} + opencode-linux-x64@1.4.3: + resolution: {integrity: sha512-RS6TsDqTUrW5sefxD1KD9Xy9mSYGXAlr2DlGrdi8vNm9e/Bt4r4u557VB7f/Uj2CxTt2Gf7OWl08ZoPlxMJ5Gg==} cpu: [x64] os: [linux] - opencode-windows-arm64@1.4.0: - resolution: {integrity: sha512-47quWER7bCGRPWRXd3fsOyu5F/T4Y65FiS05kD+PYYV4iOJymlBQ34kpcJhNBOpQLYf9HSLbJ8AaJeb5dmUi+Q==} + opencode-windows-arm64@1.4.3: + resolution: {integrity: sha512-2ViH17WpIQbRVfQaOBMi49pu73gqTQYT/4/WxFjShmRagX40/KkG18fhvyDAZrBKfkhPtdwgFsFxMSYP9F6QCQ==} cpu: [arm64] os: [win32] - opencode-windows-x64-baseline@1.4.0: - resolution: {integrity: sha512-eGK9lF70XKzf9zBO7xil9+Vl7ZJUAgLK6bG6kug6RKxD6FsydY3Y6q/3tIW0+YZ0wyINOtEbTRfUHbO5TxV4FQ==} + opencode-windows-x64-baseline@1.4.3: + resolution: {integrity: sha512-SWYDli9SAKQd/pS/hVfuq1KEsc+gnAJdv+YtBmxaHOw57y0euqLwbGFUYFq78GAMGt/RnTYWZIEUbRK/ZiX3UA==} cpu: [x64] os: [win32] - opencode-windows-x64@1.4.0: - resolution: {integrity: sha512-DQ8CoxCsmFM38U1e73+hFuB6Wu0tbn6B4R7KwcL1JhvKvQaYYiukNfuLgcjjx5D7s81NP1SWlv6lw60wN0gq8g==} + opencode-windows-x64@1.4.3: + resolution: {integrity: sha512-UxmKDIw3t4XHST6JSUWHmSrCGIEK1LRTAOpO82HBC3XkIjH78gVIeauRR6RULjWAApmy9I1C3TukO2sDUi7Gvw==} cpu: [x64] os: [win32] @@ -434,55 +434,55 @@ snapshots: '@esbuild/win32-ia32': 0.28.0 '@esbuild/win32-x64': 0.28.0 - opencode-ai@1.4.0: + opencode-ai@1.4.3: optionalDependencies: - opencode-darwin-arm64: 1.4.0 - opencode-darwin-x64: 1.4.0 - opencode-darwin-x64-baseline: 1.4.0 - opencode-linux-arm64: 1.4.0 - opencode-linux-arm64-musl: 1.4.0 - opencode-linux-x64: 1.4.0 - opencode-linux-x64-baseline: 1.4.0 - opencode-linux-x64-baseline-musl: 1.4.0 - opencode-linux-x64-musl: 1.4.0 - opencode-windows-arm64: 1.4.0 - opencode-windows-x64: 1.4.0 - opencode-windows-x64-baseline: 1.4.0 + opencode-darwin-arm64: 1.4.3 + opencode-darwin-x64: 1.4.3 + opencode-darwin-x64-baseline: 1.4.3 + opencode-linux-arm64: 1.4.3 + opencode-linux-arm64-musl: 1.4.3 + opencode-linux-x64: 1.4.3 + opencode-linux-x64-baseline: 1.4.3 + opencode-linux-x64-baseline-musl: 1.4.3 + opencode-linux-x64-musl: 1.4.3 + opencode-windows-arm64: 1.4.3 + opencode-windows-x64: 1.4.3 + opencode-windows-x64-baseline: 1.4.3 - opencode-darwin-arm64@1.4.0: + opencode-darwin-arm64@1.4.3: optional: true - opencode-darwin-x64-baseline@1.4.0: + opencode-darwin-x64-baseline@1.4.3: optional: true - opencode-darwin-x64@1.4.0: + opencode-darwin-x64@1.4.3: optional: true - opencode-linux-arm64-musl@1.4.0: + opencode-linux-arm64-musl@1.4.3: optional: true - opencode-linux-arm64@1.4.0: + opencode-linux-arm64@1.4.3: optional: true - opencode-linux-x64-baseline-musl@1.4.0: + opencode-linux-x64-baseline-musl@1.4.3: optional: true - opencode-linux-x64-baseline@1.4.0: + opencode-linux-x64-baseline@1.4.3: optional: true - opencode-linux-x64-musl@1.4.0: + opencode-linux-x64-musl@1.4.3: optional: true - opencode-linux-x64@1.4.0: + opencode-linux-x64@1.4.3: optional: true - opencode-windows-arm64@1.4.0: + opencode-windows-arm64@1.4.3: optional: true - opencode-windows-x64-baseline@1.4.0: + opencode-windows-x64-baseline@1.4.3: optional: true - opencode-windows-x64@1.4.0: + opencode-windows-x64@1.4.3: optional: true undici-types@7.18.2: {}