From 5d54e8c4aff237e0d787aa479face16774dc6d6b Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Sun, 23 Aug 2026 00:14:10 -0400 Subject: [PATCH] fix(layout): GROW elements must use configured min for minDimensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clay's CloseElement set minDimensions from content-derived size for all element types, including GROW. For a GROW root with right padding, the content-derived minDimensions.width was text_width + padding, which exceeded the terminal width. SizeContainersAlongAxis then refused to compress the root below that minimum, so the root overflowed the terminal and nested grow() spacers were clipped past the parent's right (and bottom) padding. The fix: GROW elements resolve against the parent content box, so their minimum compressible size is the user-configured min (default 0), not the natural size of their content. Non-GROW types are unchanged. Applied via patches/clay-grow-minDimensions.patch at build time, following the same pattern as patches/clay-disable-debug-tools.patch from ref/clay-debug-tools. The Makefile applies the patch idempotently before compilation and restores clay/clay.h on `make clean`. specs/renderer-spec.md §8.4 updated to document the content-box semantics: grow() minimum is the configured min, not content size. --- Makefile | 9 ++++++- patches/clay-grow-minDimensions.patch | 34 +++++++++++++++++++++++++++ specs/renderer-spec.md | 8 ++++++- 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 patches/clay-grow-minDimensions.patch diff --git a/Makefile b/Makefile index 05a2dd1..e2e21e3 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ CC = clang TARGET = clayterm.wasm SRC = src/module.c +CLAY_GROW_PATCH = patches/clay-grow-minDimensions.patch CFLAGS = --target=wasm32 -nostdlib -O2 \ -ffunction-sections -fdata-sections \ @@ -47,7 +48,12 @@ all: $(TARGET) wasm.ts DEPS = $(wildcard src/*.c src/*.h) -$(TARGET): $(DEPS) +# Fix GROW element minDimensions so they compress to the parent content box instead of +# expanding beyond the terminal boundary. Idempotent; reverted by `make clean`. +# Drop once the fix ships in upstream clay. +$(TARGET): $(DEPS) $(CLAY_GROW_PATCH) + @grep -q "GROW elements resolve against the parent content box" clay/clay.h || git -C clay apply ../$(CLAY_GROW_PATCH) + @grep -q "GROW elements resolve against the parent content box" clay/clay.h || { echo "ERROR: failed to apply $(CLAY_GROW_PATCH) to clay/clay.h" >&2; exit 1; } $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(SRC) wasm.ts: $(TARGET) @@ -55,5 +61,6 @@ wasm.ts: $(TARGET) clean: rm -f $(TARGET) wasm.ts + -git -C clay checkout -- clay.h .PHONY: all clean diff --git a/patches/clay-grow-minDimensions.patch b/patches/clay-grow-minDimensions.patch new file mode 100644 index 0000000..b2ac0da --- /dev/null +++ b/patches/clay-grow-minDimensions.patch @@ -0,0 +1,34 @@ +diff --git a/clay.h b/clay.h +index 7c967bb..5421621 100644 +--- a/clay.h ++++ b/clay.h +@@ -1921,7 +1921,14 @@ void Clay__CloseElement(void) { + layoutConfig->sizing.width.size.minMax.max = CLAY__MAXFLOAT; + } + openLayoutElement->dimensions.width = CLAY__MIN(CLAY__MAX(openLayoutElement->dimensions.width, layoutConfig->sizing.width.size.minMax.min), layoutConfig->sizing.width.size.minMax.max); +- openLayoutElement->minDimensions.width = CLAY__MIN(CLAY__MAX(openLayoutElement->minDimensions.width, layoutConfig->sizing.width.size.minMax.min), layoutConfig->sizing.width.size.minMax.max); ++ // GROW elements resolve against the parent content box and must be compressible to their ++ // configured min (default 0). Content-derived minDimensions would prevent this compression, ++ // so for GROW we use only the user-configured min, not the content-derived value. ++ if (layoutConfig->sizing.width.type == CLAY__SIZING_TYPE_GROW) { ++ openLayoutElement->minDimensions.width = layoutConfig->sizing.width.size.minMax.min; ++ } else { ++ openLayoutElement->minDimensions.width = CLAY__MIN(CLAY__MAX(openLayoutElement->minDimensions.width, layoutConfig->sizing.width.size.minMax.min), layoutConfig->sizing.width.size.minMax.max); ++ } + } else { + openLayoutElement->dimensions.width = 0; + } +@@ -1932,7 +1939,12 @@ void Clay__CloseElement(void) { + layoutConfig->sizing.height.size.minMax.max = CLAY__MAXFLOAT; + } + openLayoutElement->dimensions.height = CLAY__MIN(CLAY__MAX(openLayoutElement->dimensions.height, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); +- openLayoutElement->minDimensions.height = CLAY__MIN(CLAY__MAX(openLayoutElement->minDimensions.height, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); ++ // Same rationale as width: GROW elements use the configured min, not content-derived. ++ if (layoutConfig->sizing.height.type == CLAY__SIZING_TYPE_GROW) { ++ openLayoutElement->minDimensions.height = layoutConfig->sizing.height.size.minMax.min; ++ } else { ++ openLayoutElement->minDimensions.height = CLAY__MIN(CLAY__MAX(openLayoutElement->minDimensions.height, layoutConfig->sizing.height.size.minMax.min), layoutConfig->sizing.height.size.minMax.max); ++ } + } else { + openLayoutElement->dimensions.height = 0; + } diff --git a/specs/renderer-spec.md b/specs/renderer-spec.md index 398e78a..2289a99 100644 --- a/specs/renderer-spec.md +++ b/specs/renderer-spec.md @@ -477,10 +477,16 @@ These functions produce sizing-axis values for use in element layout configuration: ``` -grow(): SizingAxis +grow(min?: number, max?: number): SizingAxis ``` The element expands to fill available space in the parent along this axis. +Available space is computed from the **parent's content box** — the parent's +dimension minus its padding — at every nesting level, regardless of whether the +parent's own size was computed from `fixed()`, `grow()`, or any other mode. A +`grow()` element's minimum size is its configured `min` (default 0), not the +natural size of its content; this ensures it can always be compressed to fit the +parent content box when necessary. ``` fixed(value: number): SizingAxis -- 2.54.0