Skip to content

fix(grid): stretch unequal flex-col cells without the IntrinsicHeight assert (#139)#140

Merged
anilcancakir merged 2 commits into
masterfrom
fix/grid-stretch-intrinsic-flex-col
Jul 1, 2026
Merged

fix(grid): stretch unequal flex-col cells without the IntrinsicHeight assert (#139)#140
anilcancakir merged 2 commits into
masterfrom
fix/grid-stretch-intrinsic-flex-col

Conversation

@anilcancakir

@anilcancakir anilcancakir commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #139, a regression in the #126/#137 items-stretch grid. grid ... items-stretch crashed with RenderBox was not laid out (rooted at RenderIntrinsicHeightLayoutBuilder does not support returning intrinsic dimensions) the moment it had to stretch an unequal flex flex-col cell — i.e. the exact case the feature exists for. Equal-height cells hid the bug; a taller sibling triggered it.

Root cause (reproduced first)

The stretch grid wrapped each row in IntrinsicHeight + Row(crossAxisAlignment: stretch). IntrinsicHeight measures children via the intrinsic protocol. A flex flex-col cell (or one using h-full / basis-*) carries a LayoutBuilder, and LayoutBuilder cannot answer intrinsic (or dry-layout) queries — so stretching a shorter cell asserted. Same root construct as #129.

Fix

Replace IntrinsicHeight + Row(stretch) with WindEqualHeightRow, a small RenderObject that:

  1. lays each cell out with a real loose-height layout (which LayoutBuilder fully supports) to find the tallest,
  2. re-lays every cell tight to that max height,
  3. gives each cell an equal share of the width (matching the fixed column count), with spacing between columns.

Real layout instead of intrinsic queries means LayoutBuilder-bearing cells stretch correctly. Bonus: h-full inside a stretched cell now resolves too (previously documented as unsupported).

Tests

Definition of done

  • dart analyze: clean. dart format: no diff.
  • flutter test: all pass, coverage 94.4% ≥ 90% (wind_equal_height_row.dart fully covered).
  • Post-change sync: doc/layout/grid.md (mechanism + dropped the now-obsolete "no h-full on a stretched cell" caveat), skill (tokens.md + references/layouts.md, SKILL.md version 2.8.1), CHANGELOG.md. WindEqualHeightRow is an internal render helper (not exported / not a public W-widget), so the README roster is unchanged.

Summary by CodeRabbit

  • New Features

    • Added improved equal-height grid rows that size cells based on the tallest item, with consistent spacing and better support for stretched content.
    • Expanded layout behavior so grids can handle more complex children and unbounded width scenarios more reliably.
  • Bug Fixes

    • Fixed a crash in equal-height grid layouts involving uneven flex-column cells.
    • Resolved cases where stretched cells could fail to fill the full row height correctly.
  • Documentation

    • Updated grid and layout guidance to reflect the new equal-height row behavior.

… assert

grid items-stretch (#126/#137) crashed with "RenderBox was not laid out"
(rooted at RenderIntrinsicHeight -> "LayoutBuilder does not support
returning intrinsic dimensions") whenever it had to STRETCH an unequal
cell whose content is a flex flex-col (a card): such a cell carries a
LayoutBuilder (cross-axis stretch / h-full / basis-*), and IntrinsicHeight
queries child intrinsics, which LayoutBuilder cannot answer. So the
feature crashed exactly the unequal-height case it exists for.

Replace IntrinsicHeight + Row(stretch) with WindEqualHeightRow, a
RenderObject that measures each cell with a real loose-height layout and
re-lays it tight to the row's max height. Real layout is what LayoutBuilder
supports, so flex-col cells stretch correctly. Each cell also gets an equal
share of the width (matching the fixed column count). Bonus: h-full inside
a stretched cell now resolves too.

Post-change sync: wind_equal_height_row_test (stretch, width split, spacing
setter, LayoutBuilder child, hit-test, unbounded-width, empty) + grid
_stretch_test #139 case; doc/layout/grid.md, skill (tokens.md + layouts.md,
version 2.8.1), CHANGELOG.

Closes #139
Copilot AI review requested due to automatic review settings July 1, 2026 13:43
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@anilcancakir, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e706ffba-ced0-456f-972f-b4328fb97f3a

📥 Commits

Reviewing files that changed from the base of the PR and between 5fa8d8e and 9bdd566.

📒 Files selected for processing (3)
  • lib/src/widgets/w_div.dart
  • lib/src/widgets/wind_equal_height_row.dart
  • test/widgets/wind_equal_height_row_test.dart
📝 Walkthrough

Walkthrough

Replaces the IntrinsicHeight-based equal-height grid stretching in w_div.dart with a new WindEqualHeightRow widget that measures cells loosely then re-lays them tight to the tallest cell height, avoiding an intrinsic-sizing crash with flex flex-col cells. Adds tests and updates documentation and changelog accordingly.

Changes

Equal-Height Row Fix

Layer / File(s) Summary
WindEqualHeightRow render widget implementation
lib/src/widgets/wind_equal_height_row.dart
Adds a MultiChildRenderObjectWidget and custom render object performing a two-pass layout: measure children loosely to find max height, then re-lay tightly to that height with spacing between cells.
Grid items-stretch integration
lib/src/widgets/w_div.dart
Removes the IntrinsicHeight/Row/Expanded stretch composition in _buildStretchGrid and replaces it with WindEqualHeightRow(spacing: gapX, ...) using padded row children.
Regression and unit tests
test/widgets/w_div/grid_stretch_test.dart, test/widgets/wind_equal_height_row_test.dart
Adds a regression test for issue #139 (unequal flex-col cells stretch without crashing) and a full test suite for WindEqualHeightRow covering height equalization, spacing, dynamic updates, LayoutBuilder children, empty children, hit testing, and unbounded width.
Documentation and changelog updates
CHANGELOG.md, doc/layout/grid.md, skills/wind-ui/references/layouts.md, skills/wind-ui/references/tokens.md, skills/wind-ui/SKILL.md
Updates docs and changelog to describe the new real-layout, non-IntrinsicHeight equal-height mechanism and bumps skill version to 2.8.1.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WDiv
  participant WindEqualHeightRow
  participant RenderEqualHeightRow
  WDiv->>WindEqualHeightRow: build rowChildren with spacing (gapX)
  WindEqualHeightRow->>RenderEqualHeightRow: performLayout
  RenderEqualHeightRow->>RenderEqualHeightRow: measure each child (loose height)
  RenderEqualHeightRow->>RenderEqualHeightRow: compute max height across children
  RenderEqualHeightRow->>RenderEqualHeightRow: re-layout each child (tight to max height)
  RenderEqualHeightRow-->>WDiv: return sized, positioned row
Loading

Related issues: #139 (crash when stretching unequal flex-col cells in items-stretch grid)

Suggested labels: bug, layout, widgets

Suggested reviewers: fluttersdk maintainers familiar with WDiv grid layout and custom render objects

🐇 A row of cards, once tall and small,
Crashed and stumbled, IntrinsicHeight's fall,
Now measured twice, stretched nice and neat,
WindEqualHeightRow makes cells complete!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: fixing unequal flex-col grid stretching by replacing the IntrinsicHeight-based path.
Linked Issues check ✅ Passed The PR addresses #139 by using real layout instead of IntrinsicHeight and adds regression tests for unequal flex-col cells.
Out of Scope Changes check ✅ Passed The docs, changelog, tests, and skill reference updates all support the grid stretch fix and do not add unrelated scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/grid-stretch-intrinsic-flex-col

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a regression in the grid ... items-stretch implementation by removing the IntrinsicHeight-based equal-height mechanism (which asserts with LayoutBuilder children) and replacing it with a real two-pass layout render helper.

Changes:

  • Introduces WindEqualHeightRow (a RenderObject-backed widget) to measure tallest cell via real layout and then relayout all cells to that height.
  • Updates WDiv’s items-stretch grid path to use WindEqualHeightRow with gapX as spacing.
  • Updates docs/skill/changelog and adds tests for the new equal-height row and the #139 regression case.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
test/widgets/wind_equal_height_row_test.dart Adds widget tests for equal-height behavior, spacing updates, LayoutBuilder child, hit testing, and unbounded-width degradation.
test/widgets/w_div/grid_stretch_test.dart Adds a regression test covering unequal flex flex-col grid cells with nested WDiv (#139).
lib/src/widgets/wind_equal_height_row.dart Adds the intrinsic-free equal-height row render helper used by the grid items-stretch path.
lib/src/widgets/w_div.dart Replaces IntrinsicHeight + Row(stretch) with WindEqualHeightRow for items-stretch grids.
doc/layout/grid.md Updates grid docs to reflect the intrinsic-free stretch mechanism and removes the obsolete h-full caveat.
skills/wind-ui/SKILL.md Bumps skill version to 2.8.1.
skills/wind-ui/references/tokens.md Updates grid-cols-N token documentation to reflect real-layout equal-height rows and #139.
skills/wind-ui/references/layouts.md Updates grid guidance to reflect intrinsic-free stretching and #139.
CHANGELOG.md Adds a “Fixed” entry documenting the #139 regression and the switch to WindEqualHeightRow.

Comment on lines +88 to +91
expect(tester.takeException(), isNull);
// The LayoutBuilder cell was stretched to the 80px tall sibling.
expect(tester.getSize(find.text('lb').first), isNotNull);
});
Comment on lines +26 to +30
const WindEqualHeightRow({
super.key,
this.spacing = 0,
required super.children,
});
Comment on lines +53 to +58
double _spacing;
set spacing(double value) {
if (value == _spacing) return;
_spacing = value;
markNeedsLayout();
}
Comment on lines +100 to +116
// Pass 2: stretch every cell to the row height and position left to right.
double dx = 0;
child = firstChild;
while (child != null) {
child.layout(
BoxConstraints.tightFor(width: cellWidth, height: maxHeight),
parentUsesSize: true,
);
(child.parentData as _EqualHeightParentData).offset = Offset(dx, 0);
dx += cellWidth + _spacing;
child = childAfter(child);
}

final double width = constraints.maxWidth.isFinite
? constraints.maxWidth
: (dx - _spacing).clamp(0.0, double.infinity);
size = constraints.constrain(Size(width, maxHeight));
Comment on lines +1197 to +1202
// WindEqualHeightRow measures each cell with a real (loose-height) layout
// and re-lays it tight to the row max, instead of IntrinsicHeight's
// intrinsic query. A `flex flex-col` cell (which carries a LayoutBuilder)
// can then be stretched without the "LayoutBuilder does not support
// returning intrinsic dimensions" assert (#139).
rows.add(WindEqualHeightRow(spacing: gapX, children: rowChildren));

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/src/widgets/wind_equal_height_row.dart`:
- Around line 88-93: `WindEqualHeightRow` is still allowing `h-full` to use
viewport height during the first measuring pass, which can incorrectly stretch
an `items-stretch` row. Update the measuring logic in
`wind_equal_height_row.dart` so pass 1 treats `h-full` as content-sized before
tightening constraints, likely in the layout path around `child.layout` and the
equal-height measurement flow. Add a regression test for a `grid grid-cols-2
items-stretch` row with one shorter `h-full` cell to verify the row matches the
tallest sibling instead of the viewport.

In `@test/widgets/wind_equal_height_row_test.dart`:
- Around line 69-91: The test in the WindEqualHeightRow layout case does not
actually verify the claimed stretch behavior because checking that
tester.getSize(find.text('lb').first) is not null adds no meaningful assertion.
Update the testWidgets scenario for WindEqualHeightRow to assert the
LayoutBuilder child’s rendered height matches the 80px sibling height, using the
existing find.text('lb') lookup and a real size comparison rather than a null
check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d708a89c-befb-4e48-b54d-295fffa01ce7

📥 Commits

Reviewing files that changed from the base of the PR and between 3fd0f84 and 5fa8d8e.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • doc/layout/grid.md
  • lib/src/widgets/w_div.dart
  • lib/src/widgets/wind_equal_height_row.dart
  • skills/wind-ui/SKILL.md
  • skills/wind-ui/references/layouts.md
  • skills/wind-ui/references/tokens.md
  • test/widgets/w_div/grid_stretch_test.dart
  • test/widgets/wind_equal_height_row_test.dart

Comment on lines +88 to +93
child.layout(
BoxConstraints(
minWidth: cellWidth,
maxWidth: cellWidth,
maxHeight: constraints.maxHeight,
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

git ls-files lib/src/widgets/wind_equal_height_row.dart lib/src | sed -n '1,120p'

Repository: fluttersdk/wind

Length of output: 3483


🏁 Script executed:

ast-grep outline lib/src/widgets/wind_equal_height_row.dart --view expanded && printf '\n---\n' && ast-grep outline lib/src/widgets/w_div.dart --view expanded

Repository: fluttersdk/wind

Length of output: 256


🏁 Script executed:

wc -l lib/src/widgets/wind_equal_height_row.dart lib/src/widgets/w_div.dart
printf '\n--- wind_equal_height_row.dart ---\n'
cat -n lib/src/widgets/wind_equal_height_row.dart | sed -n '1,220p'
printf '\n--- w_div.dart grep ---\n'
rg -n "h-full|MediaQuery\.size\.height|maxHeight|height" lib/src/widgets/w_div.dart lib/src/widgets/wind_equal_height_row.dart

Repository: fluttersdk/wind

Length of output: 13771


🏁 Script executed:

sed -n '1140,1215p' lib/src/widgets/w_div.dart
printf '\n---\n'
sed -n '1490,1665p' lib/src/widgets/w_div.dart
printf '\n--- tests grep ---\n'
rg -n "WindEqualHeightRow|items-stretch|h-full|equal-height" test lib/src -g '*test.dart'

Repository: fluttersdk/wind

Length of output: 13415


🏁 Script executed:

sed -n '1,220p' test/widgets/w_div/grid_stretch_test.dart
printf '\n---\n'
sed -n '1,220p' test/widgets/wind_equal_height_row_test.dart

Repository: fluttersdk/wind

Length of output: 10558


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
row = Path('lib/src/widgets/wind_equal_height_row.dart').read_text()
div = Path('lib/src/widgets/w_div.dart').read_text()
checks = {
    "row_pass1_forwards_constraints_maxHeight": "maxHeight: constraints.maxHeight" in row,
    "h_full_unbounded_uses_mediaquery": "MediaQuery.of(context).size.height" in div and "h-full needs LayoutBuilder to handle unbounded vertical axis" in div,
    "stretch_grid_uses_equal_height_row": "WindEqualHeightRow(spacing: gapX, children: rowChildren)" in div,
}
print(checks)
assert all(checks.values()), checks
print("source-path supports the reported behavior")
PY

Repository: fluttersdk/wind

Length of output: 333


Avoid measuring h-full against the viewport in the first pass

WindEqualHeightRow still lets h-full resolve to MediaQuery.size.height during pass 1, so one cell can make an items-stretch row viewport-tall instead of matching the tallest content sibling. Add a regression test for a grid grid-cols-2 items-stretch row with a shorter h-full cell, and treat h-full as content-sized during the measuring pass before tightening.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/widgets/wind_equal_height_row.dart` around lines 88 - 93,
`WindEqualHeightRow` is still allowing `h-full` to use viewport height during
the first measuring pass, which can incorrectly stretch an `items-stretch` row.
Update the measuring logic in `wind_equal_height_row.dart` so pass 1 treats
`h-full` as content-sized before tightening constraints, likely in the layout
path around `child.layout` and the equal-height measurement flow. Add a
regression test for a `grid grid-cols-2 items-stretch` row with one shorter
`h-full` cell to verify the row matches the tallest sibling instead of the
viewport.

Comment thread test/widgets/wind_equal_height_row_test.dart
- Validate spacing (finite, non-negative) in both the widget constructor
  and the render-object setter (debug asserts).
- Honor the incoming height constraint: cells stretch to
  maxHeight.clamp(minHeight, maxHeight), not just the measured tallest, so a
  parent-imposed tight/min height stays consistent with the cells.
- Update the stale _buildStretchGrid dartdoc (was still describing the old
  IntrinsicHeight + h-full caveat).
- Make the LayoutBuilder-child test assert the actual stretched height (80)
  instead of a vacuous non-null check.
@anilcancakir

Copy link
Copy Markdown
Collaborator Author

Addressed all five review comments:

  • spacing validation: debug asserts (finite, non-negative) on both the widget constructor and the render-object setter.
  • height constraint: cells now stretch to maxHeight.clamp(constraints.minHeight, constraints.maxHeight), so a parent-imposed tight/min row height stays consistent with the cells (not just the measured tallest).
  • stale dartdoc: _buildStretchGrid doc rewritten to describe WindEqualHeightRow (real layout), dropping the obsolete IntrinsicHeight + no-h-full caveat.
  • vacuous test: the LayoutBuilder-child test now asserts the cell's actual stretched height (80), proving real layout rather than a never-null check.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd wind_equal_height_row.dart
echo "---"
cat -n lib/src/widgets/wind_equal_height_row.dart

Length of output: 6345


@anilcancakir Thanks for the updates — I've confirmed all four code-level changes in lib/src/widgets/wind_equal_height_row.dart:

  • Spacing validation asserts on both the constructor (Line 30) and the spacing setter (Line 58).
  • Height clamping via maxHeight.clamp(constraints.minHeight, constraints.maxHeight) (Line 110-111) so a parent-imposed height constraint stays consistent with cell sizing.
  • Dartdoc rewritten to accurately describe the real two-pass layout approach instead of the obsolete IntrinsicHeight caveat.

I'll kick off a fresh review to double-check the test file changes and everything else.

ᕙ(⇀‸↼‶)ᕗ

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@anilcancakir anilcancakir merged commit 4fe3f80 into master Jul 1, 2026
8 of 9 checks passed
@anilcancakir anilcancakir deleted the fix/grid-stretch-intrinsic-flex-col branch July 1, 2026 13:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

items-stretch asserts (RenderIntrinsicHeight) when stretching an unequal-height flex flex-col cell

2 participants