From 78feb03b837425f5e0c23902089b847ad5f916ad Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 28 Oct 2026 18:11:40 +0100 Subject: [PATCH] :bug: Default absent layout padding to zero Stored layout frames may omit padding. The padding helpers returned nil sides, so padding totals and full flex/grid layout calculation crashed on the JVM. Default absent padding to zero and share that reader between horizontal and vertical totals. Add portable regressions that prove the inputs pass the shape schema, then compare their layout calculation with explicit zero padding. Both regressions fail before the fix; existing simple/multiple checks stay green. Validate common JVM (1997 tests) and JS (1933), frontend (1742), backend (1793) and exporter (16), plus lint and formatting for all four modules. All six generated properties pass 40 programs at seed 712 in both runtimes. Golden data is unchanged. The benchmark passes with no regression flags against the existing baseline. Lower-quantile ratios: main 10/100/1000 copies 0.51/0.58/0.60, three levels 0.56, chain 0.52, library 0.53, many shapes 0.43, variant switch 0.64. The tool warns that machine/JVM differences make these rough comparisons. No performance baseline changed. AI-assisted-by: gpt-6 --- common/src/app/common/types/shape/layout.cljc | 19 +++++++-------- .../geom_bounds_layout_nil_test.cljc | 23 +++++++++++++++++++ .../common_tests/types/shape_layout_test.cljc | 10 ++++++++ 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 215d9709cf..c5eb7d6ccc 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -328,24 +328,21 @@ (defn paddings [{:keys [layout-padding-type layout-padding]}] - (let [{pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding] + (let [{pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4 + :or {pad-top 0 pad-right 0 pad-bottom 0 pad-left 0}} layout-padding] (if (= :simple layout-padding-type) [pad-top pad-right pad-top pad-right] [pad-top pad-right pad-bottom pad-left]))) (defn h-padding - [{:keys [layout-padding-type layout-padding]}] - (let [{pad-right :p2 pad-left :p4} layout-padding] - (if (= :simple layout-padding-type) - (+ pad-right pad-right) - (+ pad-right pad-left)))) + [shape] + (let [[_ right _ left] (paddings shape)] + (+ right left))) (defn v-padding - [{:keys [layout-padding-type layout-padding]}] - (let [{pad-top :p1 pad-bottom :p3} layout-padding] - (if (= :simple layout-padding-type) - (+ pad-top pad-top) - (+ pad-top pad-bottom)))) + [shape] + (let [[top _ bottom _] (paddings shape)] + (+ top bottom))) (defn padding-type-for "`:simple` when top≈bottom and left≈right, `:multiple` otherwise (nil sides = 0)." diff --git a/common/test/common_tests/geom_bounds_layout_nil_test.cljc b/common/test/common_tests/geom_bounds_layout_nil_test.cljc index dda9fa957f..a0b5d728a1 100644 --- a/common/test/common_tests/geom_bounds_layout_nil_test.cljc +++ b/common/test/common_tests/geom_bounds_layout_nil_test.cljc @@ -11,7 +11,9 @@ [app.common.geom.point :as gpt] [app.common.geom.rect :as grc] [app.common.geom.shapes.flex-layout.bounds :as fb] + [app.common.geom.shapes.flex-layout.layout-data :as fld] [app.common.geom.shapes.grid-layout.bounds :as gb] + [app.common.geom.shapes.grid-layout.layout-data :as gld] [app.common.geom.shapes.min-size-layout :as msl] [app.common.types.shape :as cts] [app.common.types.shape.layout :as ctl] @@ -89,6 +91,27 @@ (apply dissoc full exclude-ids) full))) +(t/deftest layout-calculation-without-optional-padding + (doseq [layout-type [:flex :grid] + padding-type [nil :simple :multiple]] + (let [parent-id (uuid/next) + child-id (uuid/next) + child (make-rect child-id 10 10 50 50) + parent (cond-> (dissoc (case layout-type + :flex (make-flex-frame parent-id [child-id]) + :grid (make-grid-frame parent-id [child-id])) + :layout-padding :layout-padding-type) + padding-type (assoc :layout-padding-type padding-type)) + objects (make-objects [parent child]) + bounds (bounds-map-from-objects objects) + calculate (case layout-type :flex fld/calc-layout-data :grid gld/calc-layout-data) + explicit (assoc parent :layout-padding {:p1 0 :p2 0 :p3 0 :p4 0})] + (t/is (cts/valid-shape? parent)) + (t/is (= (calculate explicit (:points parent) [[(:points child) child]] bounds + (assoc objects parent-id explicit)) + (calculate parent (:points parent) [[(:points child) child]] bounds objects)) + (str "Absent padding must keep " layout-type " layout geometry"))))) + ;; ---- Tests for flex layout bounds with nil bounds ---- (t/deftest layout-content-points-with-missing-parent-bounds diff --git a/common/test/common_tests/types/shape_layout_test.cljc b/common/test/common_tests/types/shape_layout_test.cljc index d32ea5a6b6..4ec0ef268b 100644 --- a/common/test/common_tests/types/shape_layout_test.cljc +++ b/common/test/common_tests/types/shape_layout_test.cljc @@ -354,6 +354,16 @@ :layout-padding {:p1 8 :p2 10 :p3 99 :p4 99})] (t/is (= 16 (layout/v-padding shape)))))) +(t/deftest absent-layout-padding-means-zero + (doseq [layout-type [:flex :grid] + padding-type [nil :simple :multiple]] + (let [shape (cond-> (make-frame :layout layout-type) + padding-type (assoc :layout-padding-type padding-type))] + (t/is (cts/valid-shape? shape) "Padding is optional in stored shapes") + (t/is (= [0 0 0 0] (layout/paddings shape))) + (t/is (zero? (layout/h-padding shape))) + (t/is (zero? (layout/v-padding shape)))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; child-min/max-width/height ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;