Flexbox hardening: smart column stretch + grow/basis tokens + WText baseline#97
Conversation
A bare WText (no Scaffold/MaterialApp) now falls back to Colors.black and injects a Directionality(ltr) when none is inherited, instead of Flutter's debug yellow-underline. Explicit text-*/foregroundColor/textStyle still win.
Route grow/grow-0/basis-* through canParse; grow=flex:1, basis-* sets the child's initial main-axis size (WindStyle.basisFactor/basisSize). flex-none (CSS flex:0 0 auto) no longer maps to a shrinking FlexFit.
flex-col with no explicit items-* now wraps each width-less WDiv child in SizedBox(width: infinity) so it fills the column (CSS align-items: stretch default); explicit-width/flex/absolute children and Rows are untouched. basis-* resolves against the flex's measured main extent.
CHANGELOG, doc/layout/flexbox, and the wind-ui skill (SKILL.md v2.0.4 + tokens/layouts/divergence refs) document the new flex defaults and WText fix.
In-process geometry suite covering navbar/media-object/holy-grail/sticky-footer/ centered/responsive/card/toolbar/wrap-grid/form-row/pricing/chip-list/split/ list-item/hero; asserts flex-1 distribution, shrink-0 intrinsic size, column smart-stretch fill, alignment, and md: responsive flips.
Extend the layout/flex_grow gallery page with grow vs grow-0, basis-* fractional sizing, and a smart-column-stretch section (flex-col children fill width by default vs items-start sizing to content).
…ness-aware WText baseline Post-review fixes: - Smart column stretch wrapped children in SizedBox(width: infinity), which THREW under an unbounded-width column (bare Row slot / UnconstrainedBox / horizontal scroll). Gate the stretch on a finite incoming maxWidth via a LayoutBuilder (only when a stretch-eligible child exists); unbounded columns now degrade to content-sized children instead of crashing. Regression test added. - WText baseline fallback is now brightness-aware (white on dark, black on light) instead of an always-black color that was invisible on dark backgrounds without a Material ancestor. - Add basis-not-double-stretched + dark-baseline + bounded-scroll tests.
📝 WalkthroughWalkthroughThis PR introduces ChangesFlex-Basis & Text Rendering Enhancements
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsStopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a Comment |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
Hardens Wind’s flexbox behavior and documentation by adding column-only cross-axis stretch defaults, introducing new flex sizing tokens (grow, grow-0, basis-*), and making WText render robustly even without a Material/Scaffold ancestor. This fits Wind’s goal of providing Tailwind-like, utility-first layout semantics with predictable Flutter widget-tree composition.
Changes:
- Adds “smart column stretch” for
flex flex-colso width-lessWDivchildren default to filling the column width (CSS-likealign-items: stretchbehavior), with guards for unbounded width. - Introduces/extends flex child sizing tokens (
grow,grow-0,basis-*) and correctsflex-nonesemantics, plus updatesWindStyleto carry basis metadata. - Improves
WTextbaseline rendering by guaranteeing a non-null color fallback and injectingDirectionalitywhen missing; adds extensive geometry/baseline test coverage and updates docs/skill references/examples.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
lib/src/widgets/w_div.dart |
Implements smart column stretch and main-axis basis-* sizing; adjusts Row shrink-wrapping behavior. |
lib/src/parser/parsers/flexbox_grid_parser.dart |
Adds parsing support for grow/grow-0 and basis-*, and updates flex token docs in-parser. |
lib/src/parser/wind_style.dart |
Adds basisFactor / basisSize to the immutable style model. |
lib/src/widgets/w_text.dart |
Adds baseline color fallback + missing Directionality injection for bare WText rendering. |
test/widgets/w_text/baseline_test.dart |
New widget tests covering bare-context WText color + directionality guarantees. |
test/widgets/w_div/flex_tokens_test.dart |
New widget tests for grow, grow-0, basis-*, and corrected flex-none. |
test/widgets/w_div/flex_stretch_test.dart |
New widget tests for column-only smart stretch + edge cases (bounded/unbounded width). |
test/parser/parsers/flexbox_grid_parser_test.dart |
Extends parser tests to cover grow/grow-0, basis-*, and flex-none expectations. |
test/flex/flexbox_scenarios_test.dart |
Adds a broad, geometry-based suite of canonical flexbox scenario tests. |
doc/layout/flexbox.md |
Documents new column-stretch default and new/updated flex sizing tokens. |
example/lib/pages/layout/flex_grow.dart |
Updates example page to demonstrate grow, grow-0, basis-*, and smart column stretch. |
skills/wind-ui/SKILL.md |
Bumps skill version metadata to v2.0.4. |
skills/wind-ui/references/tokens.md |
Updates token reference docs for flex-none, grow-0, and basis-*. |
skills/wind-ui/references/layouts.md |
Adds guidance about default column stretching semantics. |
skills/wind-ui/references/tailwind-divergence.md |
Documents new flex-none/basis-* support and updates WText baseline note. |
CHANGELOG.md |
Adds release notes for grow/basis/stretch and WText baseline changes. |
| /// Guarantees two baseline requirements when no Material/Scaffold ancestor | ||
| /// is present so Flutter's debug yellow-underline fallback never appears: | ||
| /// 1. A non-null text color (falls back to [Colors.black]). | ||
| /// 2. A [Directionality] ancestor (defaults to [TextDirection.ltr]). |
| | `text-7xl` / `8xl` / `9xl` | silently capped (max is `text-6xl`) | `text-6xl` or arbitrary `text-[96px]` | | ||
|
|
||
| Reminder: a wind page needs a Material ancestor (a `Scaffold`) for `WText` to inherit a default text style; without one, Flutter renders the yellow-underline fallback. Real apps always have a `Scaffold`, so this only bites bare `WDiv > WText` route bodies. | ||
| Note: `WText` is self-contained regarding its baseline rendering. When no Material/Scaffold ancestor supplies a `DefaultTextStyle` color, `WText` falls back to `Colors.black` internally; it also injects a `Directionality(ltr)` wrapper when no `Directionality` is inherited. Explicit `text-*` className, `foregroundColor`, and `textStyle` props override the fallback and are unaffected. |
|
|
||
| ### Fixed | ||
|
|
||
| - `WText` bare rendering: a `WText` used outside a `MaterialApp` / `Scaffold` now renders with a baseline `Colors.black` color instead of Flutter's debug yellow-underline fallback. When no `Directionality` ancestor exists, `WText` injects one defaulting to `TextDirection.ltr`. Explicitly supplied colors (`className text-*`, `foregroundColor`, `textStyle`) still win and are unaffected. |
| if (_hasFlexClass(className)) return false; | ||
| if (_hasExplicitCrossWidth(className, context)) return false; | ||
| if (className != null && | ||
| className.isNotEmpty && | ||
| className.contains('absolute')) { |
| static bool _hasExplicitCrossWidth(String? className, BuildContext context) { | ||
| if (className == null || className.isEmpty) return false; | ||
| final styles = WindParser.parse(className, context); | ||
| return styles.width != null || | ||
| styles.widthFactor != null || | ||
| (styles.constraints?.minWidth != null && | ||
| styles.constraints!.minWidth > 0) || | ||
| (styles.constraints?.maxWidth != null && | ||
| styles.constraints!.maxWidth != double.infinity); | ||
| } |
| // `basis-1/2`, `basis-full` (fractional) and `basis-[120px]` (fixed). | ||
| // Arbitrary precedes the theme/fraction form per the parser convention. | ||
| if (basisFactor == null && basisSize == null) { | ||
| final arbitraryMatch = _basisArbitraryRegex.firstMatch(className); | ||
| if (arbitraryMatch != null) { |
There was a problem hiding this comment.
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/w_text.dart`:
- Around line 157-161: Update the doc comment above the widget that guarantees
baseline requirements (the comment describing non-null text color and
Directionality) to reflect the actual brightness-aware fallback implemented in
the code: instead of stating it falls back to Colors.black, note that the
implementation selects Colors.white for dark platform brightness and
Colors.black for light brightness (referencing the code paths that use
Colors.white / Colors.black when computing the default text color), while still
guaranteeing a non-null text color and a default TextDirection.ltr when no
Material/Scaffold ancestor is present.
In `@skills/wind-ui/references/tailwind-divergence.md`:
- Line 341: The note about WText's baseline fallback is inaccurate: update the
comment text referencing WText (and its baseline rendering and DefaultTextStyle
behavior) to state that the fallback color is brightness-dependent — i.e., WText
uses Colors.white on dark platforms and Colors.black on light platforms (unless
overridden by explicit text-* className, foregroundColor, or textStyle);
alternatively, add the phrase "brightness-dependent fallback" so readers
understand it's conditional rather than always black.
🪄 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: 459fd2d0-7878-4137-a567-c52e201805d5
📒 Files selected for processing (16)
CHANGELOG.mddoc/layout/flexbox.mdexample/lib/pages/layout/flex_grow.dartlib/src/parser/parsers/flexbox_grid_parser.dartlib/src/parser/wind_style.dartlib/src/widgets/w_div.dartlib/src/widgets/w_text.dartskills/wind-ui/SKILL.mdskills/wind-ui/references/layouts.mdskills/wind-ui/references/tailwind-divergence.mdskills/wind-ui/references/tokens.mdtest/flex/flexbox_scenarios_test.darttest/parser/parsers/flexbox_grid_parser_test.darttest/widgets/w_div/flex_stretch_test.darttest/widgets/w_div/flex_tokens_test.darttest/widgets/w_text/baseline_test.dart
| /// | ||
| /// Guarantees two baseline requirements when no Material/Scaffold ancestor | ||
| /// is present so Flutter's debug yellow-underline fallback never appears: | ||
| /// 1. A non-null text color (falls back to [Colors.black]). | ||
| /// 2. A [Directionality] ancestor (defaults to [TextDirection.ltr]). |
There was a problem hiding this comment.
Update documentation to reflect brightness-aware color fallback.
The documentation states the fallback is Colors.black, but the implementation (lines 186-191) actually chooses between Colors.white (dark) and Colors.black (light) based on platform brightness. Update the comment to match the implementation.
📝 Proposed documentation fix
/// Guarantees two baseline requirements when no Material/Scaffold ancestor
/// is present so Flutter's debug yellow-underline fallback never appears:
- /// 1. A non-null text color (falls back to [Colors.black]).
+ /// 1. A non-null text color (falls back to white on dark, black on light).
/// 2. A [Directionality] ancestor (defaults to [TextDirection.ltr]).📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| /// | |
| /// Guarantees two baseline requirements when no Material/Scaffold ancestor | |
| /// is present so Flutter's debug yellow-underline fallback never appears: | |
| /// 1. A non-null text color (falls back to [Colors.black]). | |
| /// 2. A [Directionality] ancestor (defaults to [TextDirection.ltr]). | |
| /// | |
| /// Guarantees two baseline requirements when no Material/Scaffold ancestor | |
| /// is present so Flutter's debug yellow-underline fallback never appears: | |
| /// 1. A non-null text color (falls back to white on dark, black on light). | |
| /// 2. A [Directionality] ancestor (defaults to [TextDirection.ltr]). |
🤖 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/w_text.dart` around lines 157 - 161, Update the doc comment
above the widget that guarantees baseline requirements (the comment describing
non-null text color and Directionality) to reflect the actual brightness-aware
fallback implemented in the code: instead of stating it falls back to
Colors.black, note that the implementation selects Colors.white for dark
platform brightness and Colors.black for light brightness (referencing the code
paths that use Colors.white / Colors.black when computing the default text
color), while still guaranteeing a non-null text color and a default
TextDirection.ltr when no Material/Scaffold ancestor is present.
| | `text-7xl` / `8xl` / `9xl` | silently capped (max is `text-6xl`) | `text-6xl` or arbitrary `text-[96px]` | | ||
|
|
||
| Reminder: a wind page needs a Material ancestor (a `Scaffold`) for `WText` to inherit a default text style; without one, Flutter renders the yellow-underline fallback. Real apps always have a `Scaffold`, so this only bites bare `WDiv > WText` route bodies. | ||
| Note: `WText` is self-contained regarding its baseline rendering. When no Material/Scaffold ancestor supplies a `DefaultTextStyle` color, `WText` falls back to `Colors.black` internally; it also injects a `Directionality(ltr)` wrapper when no `Directionality` is inherited. Explicit `text-*` className, `foregroundColor`, and `textStyle` props override the fallback and are unaffected. |
There was a problem hiding this comment.
Clarify brightness-dependent baseline color fallback.
The note states WText "falls back to Colors.black internally," but the implementation applies a brightness-aware fallback: Colors.white on dark platforms and Colors.black on light platforms. The test suite confirms this behavior (baseline_test.dart line 73-90: "white on dark").
Consider updating the note to reflect the conditional fallback or at least mention "brightness-dependent" so readers don't assume it's always black.
🤖 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 `@skills/wind-ui/references/tailwind-divergence.md` at line 341, The note about
WText's baseline fallback is inaccurate: update the comment text referencing
WText (and its baseline rendering and DefaultTextStyle behavior) to state that
the fallback color is brightness-dependent — i.e., WText uses Colors.white on
dark platforms and Colors.black on light platforms (unless overridden by
explicit text-* className, foregroundColor, or textStyle); alternatively, add
the phrase "brightness-dependent fallback" so readers understand it's
conditional rather than always black.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
lib/src/widgets/w_div.dart:858
_hasFlexClassonly checks forflex-1..flex-5via substring matches. The parser supports anyflex-N, and there are other self-wrapping flex tokens (shrink,flex-auto, etc.). Missing these can cause the parent Row/Column logic to wrap a child that will also self-wrap inExpanded/Flexible, leading toParentDataWidgetassertion failures (and also breaks the new smart-stretch skip logic for flex children).
static bool _hasFlexClass(String? className) {
if (className == null) return false;
return className.contains('flex-1') ||
className.contains('flex-2') ||
className.contains('flex-3') ||
className.contains('flex-4') ||
className.contains('flex-5');
}
| static bool _hasExplicitCrossWidth(String? className, BuildContext context) { | ||
| if (className == null || className.isEmpty) return false; | ||
| final styles = WindParser.parse(className, context); | ||
| return styles.width != null || |
| if (_hasFlexClass(className)) return false; | ||
| if (_hasExplicitCrossWidth(className, context)) return false; | ||
| if (className != null && |
| /// Guarantees two baseline requirements when no Material/Scaffold ancestor | ||
| /// is present so Flutter's debug yellow-underline fallback never appears: | ||
| /// 1. A non-null text color (falls back to [Colors.black]). | ||
| /// 2. A [Directionality] ancestor (defaults to [TextDirection.ltr]). |
| | `text-7xl` / `8xl` / `9xl` | silently capped (max is `text-6xl`) | `text-6xl` or arbitrary `text-[96px]` | | ||
|
|
||
| Reminder: a wind page needs a Material ancestor (a `Scaffold`) for `WText` to inherit a default text style; without one, Flutter renders the yellow-underline fallback. Real apps always have a `Scaffold`, so this only bites bare `WDiv > WText` route bodies. | ||
| Note: `WText` is self-contained regarding its baseline rendering. When no Material/Scaffold ancestor supplies a `DefaultTextStyle` color, `WText` falls back to `Colors.black` internally; it also injects a `Directionality(ltr)` wrapper when no `Directionality` is inherited. Explicit `text-*` className, `foregroundColor`, and `textStyle` props override the fallback and are unaffected. |
|
|
||
| ### Fixed | ||
|
|
||
| - `WText` bare rendering: a `WText` used outside a `MaterialApp` / `Scaffold` now renders with a baseline `Colors.black` color instead of Flutter's debug yellow-underline fallback. When no `Directionality` ancestor exists, `WText` injects one defaulting to `TextDirection.ltr`. Explicitly supplied colors (`className text-*`, `foregroundColor`, `textStyle`) still win and are unaffected. |
…ren; honor last-class-wins for no-grow/no-shrink resets Address PR #97 review (Copilot + CodeRabbit): B (crash): a column smart-stretch SizedBox or a Row Flexible wrapped a child that self-wraps in Expanded/Flexible (grow, flex-grow, flex-auto, flex-initial, shrink, flex-shrink, flex-N), throwing 'Incorrect use of ParentDataWidget'. The old _hasFlexClass only matched flex-1..5 and missed every newly-added token. Replaced it with prefix-agnostic _selfWrapsInFlex used by both the column stretch gate and the row Flexible gate, so md:grow / hover:flex-1 are caught too. Empirically reproduced (column grow/flex-auto, row grow all asserted) before the fix; regression suite in test/flex/flex_self_wrap_test.dart. C: _hasExplicitCrossWidth now scans tokens prefix-agnostically so a state/breakpoint-conditional width (hover:w-32, md:max-w-sm) still disables the stretch wrap; dropped the unused context parse. D (last-class-wins): grow-0 / shrink-0 / flex-none now claim their flex/flexFit slot with a null value during reverse iteration, so a later no-grow/no-shrink token cancels an earlier grow/shrink instead of being silently ignored. flex-none cancels both. Updated the two parser tests that encoded the old (order-independent) behavior. A (docs): WText baseline fallback is brightness-aware (white on dark, black on light); corrected the w_text docstring, tailwind-divergence, CHANGELOG. Also corrected the smart-stretch exclusion list in CHANGELOG, doc/layout/flexbox, and skills/layouts: shrink-0/flex-none DO stretch cross-axis (flex-shrink is main-axis only, matching CSS). dart analyze 0 issues; dart format clean; flutter test 1323 pass; coverage 90.6%.
…h-wtext # Conflicts: # skills/wind-ui/references/tailwind-divergence.md
Flexbox foundation hardening + WText baseline robustness
Addresses the reported "child doesn't take width without
w-fullinside a flex" pain point + makesWTextself-sufficient, with an A-to-Z flexbox test suite.Smart column cross-axis stretch (the headline)
A
flex flex-colwith no explicititems-*now stretches each width-lessWDivchild to the column width by default (CSSalign-items: stretch) — now-fullneeded. Scoped for safety (oracle-reviewed):SizedBox(width: infinity), not a parentcrossAxisAlignmentflip, so explicit-width (w-32/w-1/2/w-full),flex-*,shrink-0/flex-none, and absolute children keep their own sizing;items-start/items-centeropts out; non-WDivchildren untouched.start(cross-axis = height =RenderFlex-crash-prone);items-stretchstays the Row opt-in.LayoutBuilder(only for stretch-eligible columns) gates the wrap; an unbounded-width column degrades to content-sized children instead of forcing an infinite width. (This guard was added after review — an empirical probe caught thatSizedBox(infinity)throws under unbounded width, which the reviewers' static analysis missed.)New flex tokens + fix
grow/grow-0(Tailwind shorthands);basis-*(1/2,1/3,1/4,full,[Npx]) → initial main-axis size (approximates flex-basis; documented).flex-nonecorrected toflex: 0 0 auto(no grow, no shrink) — previously mapped to a shrinkingFlexFit.WText baseline robustness
A bare
WText(no Material/Scaffold ancestor) no longer shows Flutter's debug yellow-underline: it falls back to a brightness-aware color (white on dark, black on light) and injectsDirectionality(ltr)when none is inherited. Explicittext-*/foregroundColor/textStylestill win.Tests
test/flex/flexbox_scenarios_test.dart— 15 canonical Tailwind flexbox layouts (navbar/media-object/holy-grail/sticky-footer/centered/responsive/card/toolbar/wrap-grid/form-row/pricing/chip-list/split/list-item/hero) asserting real geometry.Verification
dart analyze0 ·dart formatno diff ·flutter test1305 pass (+19) · coverage 90.5% · publish dry-run 0 warnings · pana 160/160 wasm-ready.5-surface sync applied (CHANGELOG, doc/layout/flexbox, skill v2.0.4 + tokens/layouts/divergence, example gallery). Planned/executed via
/ac:plan→/ac:execute(complex, auto); deep-review + oracle both APPROVED.Summary by CodeRabbit
New Features
basis-*utility tokens for flexible flex-basis sizing (fractional and fixed pixel values).grow/grow-0tokens as aliases for flex growth control.flex-colcontainers.Bug Fixes
WTextrendering to safely handle missingMaterialApp,Scaffold, orDirectionalityancestors.flex-nonesemantics to prevent unintended shrinking.Documentation