🐛 Keep derived layout paddings non-negative (#12129)

Adding a flex or grid layout to a board with children derives the
paddings from the distance between the board and its children. A child
that overflows the board, or floating point noise after "Resize board
to fit content", gave negative paddings. The shape schema rejects them
since paddings must be non-negative, so update-file failed and autosave
stopped. Clamp the derived paddings to 0, as the derived gaps already
are.

Closes #12107

AI-assisted-by: claude-opus-5-5
This commit is contained in:
Eva Marco 2026-10-06 16:34:28 +02:00 committed by GitHub
parent 51ae83202e
commit fcbb8f1fc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 60 additions and 4 deletions

View File

@ -88,8 +88,8 @@
parent-selrect (:selrect parent)
padding (when (and (not (nil? parent)) (> (count shapes) 0))
{:p1 (- min-y (:y1 parent-selrect))
:p2 (- min-x (:x1 parent-selrect))})]
{:p1 (max 0 (- min-y (:y1 parent-selrect)))
:p2 (max 0 (- min-x (:x1 parent-selrect)))})]
(cond-> {:layout-flex-dir direction :layout-gap layout-gap}
(not (nil? padding))

View File

@ -154,8 +154,8 @@
layout-grid-columns (mapv (constantly ctl/default-track-value) cols)
parent-childs-vector (gpt/to-vec (gpo/origin (:points parent)) (gpt/point all-shapes-rect))
p-left (:x parent-childs-vector)
p-top (:y parent-childs-vector)]
p-left (max 0 (:x parent-childs-vector))
p-top (max 0 (:y parent-childs-vector))]
(-> {:layout-grid-columns layout-grid-columns
:layout-grid-rows layout-grid-rows

View File

@ -7,6 +7,7 @@
(ns common-tests.geom-flex-layout-test
(:require
[app.common.geom.rect :as grc]
[app.common.geom.shapes.flex-layout.params :as flparams]
[app.common.geom.shapes.flex-layout.positions :as flp]
[app.common.math :as mth]
[app.common.types.shape :as cts]
@ -104,3 +105,33 @@
:layout-justify-content :end
:x 0 :y 0 :width 100 :height 100})]
(t/is (not (ctl/v-end? frame))))))
;; ---- calculate-params paddings ----
;;
;; Adding a flex layout derives the paddings from the distance between the
;; board and its children. A child that overflows the board, or floating
;; point noise after "Resize board to fit content", gave negative paddings
;; that the shape schema rejects, which broke autosave (#12107).
(defn- params-for-child
[child-attrs]
(let [parent (cts/setup-shape {:type :frame :x 0 :y 0 :width 100 :height 100})
child (cts/setup-shape (merge {:type :rect :parent-id (:id parent)} child-attrs))
parent (assoc parent :shapes [(:id child)])
objects {(:id parent) parent (:id child) child}]
(flparams/calculate-params objects [child] parent)))
(t/deftest calculate-params-keeps-positive-padding
(t/is (= {:p1 20 :p2 10 :p3 20 :p4 10}
(:layout-padding (params-for-child {:x 10 :y 20 :width 50 :height 50})))))
(t/deftest calculate-params-clamps-overflowing-child-padding
(t/is (= {:p1 0 :p2 0 :p3 0 :p4 0}
(:layout-padding (params-for-child {:x -10 :y -5 :width 150 :height 150})))))
(t/deftest calculate-params-clamps-float-noise-padding
(let [padding (:layout-padding
(params-for-child {:x -1.1728368917829357E-5
:y -2.1457672119140625E-6
:width 100 :height 100}))]
(t/is (every? #(not (neg? %)) (vals padding)))))

View File

@ -11,6 +11,7 @@
[app.common.geom.modifiers]
[app.common.geom.rect :as grc]
[app.common.geom.shapes.grid-layout.layout-data :as gld]
[app.common.geom.shapes.grid-layout.params :as glparams]
[app.common.math :as mth]
[app.common.types.shape :as cts]
[clojure.test :as t]))
@ -408,3 +409,27 @@
result (gld/set-auto-multi-span (auto-col-parent) tracks cmap cells frame-bounds {} :column)]
(t/is (mth/close? 100.0 (:size (nth result 0)) 0.001))
(t/is (mth/close? 100.0 (:size (nth result 1)) 0.001)))))
;; ---------------------------------------------------------------------------
;; calculate-params: derived paddings are never negative
;; ---------------------------------------------------------------------------
;;
;; Adding a grid layout derives the paddings from the children. A child that
;; overflows the board gave negative paddings that the shape schema rejects,
;; which broke autosave (#12107).
(defn- grid-params-for-children
[& children-attrs]
(let [parent (cts/setup-shape {:type :frame :x 0 :y 0 :width 100 :height 100})
children (mapv #(cts/setup-shape (merge {:type :rect :parent-id (:id parent)} %))
children-attrs)]
(glparams/calculate-params nil children parent)))
(t/deftest grid-calculate-params-keeps-positive-padding
(let [params (grid-params-for-children {:x 10 :y 20 :width 30 :height 30})]
(t/is (mth/close? 20 (get-in params [:layout-padding :p1])))
(t/is (mth/close? 10 (get-in params [:layout-padding :p2])))))
(t/deftest grid-calculate-params-clamps-overflowing-child-padding
(let [params (grid-params-for-children {:x -10 :y -5 :width 150 :height 150})]
(t/is (= {:p1 0 :p2 0 :p3 0 :p4 0} (:layout-padding params)))))