From bb4d4f357048a3662a407ab59d2b5b4056df579e Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Sat, 24 Oct 2026 09:39:14 +0200 Subject: [PATCH] :bug: Handle empty flex layouts when calculating line positions Skip per-line stretch division and baseline placement when a flex layout has no lines. Removing a component's final child then resizing a copy could divide by zero on the JVM (F169). The public layout regression fails four assertions before the fix. It covers row and column layouts with six content alignments. Fresh flex, swap and variant suites pass 125 tests and 596 assertions, and the shrunk structure program passes. Pure goldens match all 1149 assertions. AI-assisted-by: GPT-6 --- .serena/memories/common/layout-grid-subtleties.md | 9 ++++++++- .../common/geom/shapes/flex_layout/layout_data.cljc | 7 ++++--- common/test/common_tests/geom_flex_layout_test.cljc | 10 ++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.serena/memories/common/layout-grid-subtleties.md b/.serena/memories/common/layout-grid-subtleties.md index 2a48b9d149..845ca64f8b 100644 --- a/.serena/memories/common/layout-grid-subtleties.md +++ b/.serena/memories/common/layout-grid-subtleties.md @@ -5,6 +5,13 @@ - Layout container data and child layout-item data are removed by different helpers. Do not assume clearing a layout frame also clears all child layout metadata. - Layout data can affect both container attrs and immediate child attrs; validate behavior for both sides when changing cleanup or propagation. +## Empty flex layouts + +- No children means no layout lines. Skip stretch-per-line division and + baseline placement when the line count is zero. Both row and column + layouts can become empty after component sync; resizing them must still + return valid layout data (F169). + ## Grid assignment - Grid `assign-cells` ensures at least one column and row, skips absolute-position children, creates non-tracked rows/cols when children exceed tracked cells, and asserts that assigned cells do not overlap. @@ -13,4 +20,4 @@ - The `:reorder-children` change it emits is refused on parents inside component copies unless `allow-altering-copies` (same rule as `:mov-objects`); `pcb/reorder-grid-children` also skips copy grids producer-side. Copy child order is owned by the component sync engine. - Grid deassignment removes cells for shapes that are no longer direct children or have become absolute-positioned. - Auto-positioning is not just sorting: some auto cells are converted to manual when empty/manual/span state would break the auto sequence, then auto single-span items can be compacted. -- `fix-overlaps` is marked dev-only and removes one overlapping cell, preferring empty cells first. Avoid depending on it as normal production repair. \ No newline at end of file +- `fix-overlaps` is marked dev-only and removes one overlapping cell, preferring empty cells first. Avoid depending on it as normal production repair. diff --git a/common/src/app/common/geom/shapes/flex_layout/layout_data.cljc b/common/src/app/common/geom/shapes/flex_layout/layout_data.cljc index da81f5b876..c960d4414d 100644 --- a/common/src/app/common/geom/shapes/flex_layout/layout_data.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/layout_data.cljc @@ -224,12 +224,12 @@ ;; When align-items is stretch we need to adjust the main axis size to grow for the full content stretch-width-fix - (if (and col? (ctl/content-stretch? parent) (not auto-width?)) + (if (and (pos? num-lines) col? (ctl/content-stretch? parent) (not auto-width?)) (/ (- layout-width (* layout-gap-col (dec num-lines)) total-max-width) num-lines) 0) stretch-height-fix - (if (and row? (ctl/content-stretch? parent) (not auto-height?)) + (if (and (pos? num-lines) row? (ctl/content-stretch? parent) (not auto-height?)) (/ (- layout-height (* layout-gap-row (dec num-lines)) total-max-height) num-lines) 0) @@ -300,7 +300,8 @@ [total-width total-height] (->> layout-lines (reduce add-lines [0 0])) - base-p (flp/get-base-line parent layout-bounds total-width total-height num-lines)] + base-p (when (pos? num-lines) + (flp/get-base-line parent layout-bounds total-width total-height num-lines))] (->> layout-lines (reduce (partial add-starts total-width total-height num-lines) [(transient []) base-p]) diff --git a/common/test/common_tests/geom_flex_layout_test.cljc b/common/test/common_tests/geom_flex_layout_test.cljc index 746b2cbef1..2746ead5ed 100644 --- a/common/test/common_tests/geom_flex_layout_test.cljc +++ b/common/test/common_tests/geom_flex_layout_test.cljc @@ -34,6 +34,16 @@ [rect] (grc/rect->points rect)) +(t/deftest empty-wrapped-layout-has-no-lines + (doseq [direction [:row :column] + alignment [:stretch :space-around :space-evenly :space-between :center :end]] + (let [frame (make-col-frame :layout-flex-dir direction + :layout-align-content alignment + :layout-padding {:p1 0 :p2 0 :p3 0 :p4 0} + :layout-padding-type :simple) + bounds (rect->bounds (grc/make-rect 0 0 200 200))] + (t/is (= [] (:layout-lines (fld/calc-layout-data frame bounds [] {} {}))))))) + ;; ---- get-base-line (around? branch) ---- ;; ;; Bug: in positions.cljc the col? + around? branch had a mis-parenthesised