Skip to content

fix(components): stop declaring query containers nothing queries - #3082

Open
mfal wants to merge 9 commits into
mainfrom
fix/section-container-type-2655
Open

fix(components): stop declaring query containers nothing queries#3082
mfal wants to merge 9 commits into
mainfrom
fix/section-container-type-2655

Conversation

@mfal

@mfal mfal commented Sep 2, 2026

Copy link
Copy Markdown
Member

Section stops declaring container-type: inline-size. The underlying bug is a Blink layout-invalidation bug, not a Flow bug — this change removes Flow's exposure to it, it does not fix Chrome. Under a container-type element an async descendant size change can fail to invalidate the container's block-size layout, so it renders at the stale short height and the ancestor clips the content until a later style recalc (the reporter's hover, anywhere on the page) forces the flush.

Which container queries actually depend on Section

None. An exhaustive sweep (@container, container-type, container-name, cq* units across packages/ and apps/, all file types) finds nine @container sites, and every one resolves against a container the querying component declares itself:

@container site resolves against
ColumnLayout.module.scss (3) .columnLayoutContainer, its own parent element
List/components/Header (5) .header, the element the rules are nested in
containerBreakpointSizes mixin via SegmentedControl .segmentedControl
apps/docs page.module.scss :global(.flow--column-layout--column-layout-container), named in the selector
apps/docs ComposingCodeExample .columnLayoutContainer — it is a direct child of ColumnLayout, not of a Section

Section got its container-type in 2becdb5 ("feat: support Switch in Sections") for a @container (max-width: 550px) query on its own .header. That query left when Header became its own component. The containment has been unused since.

So the narrowest correct scope for Section is not a container at all, which also answers the nesting question: Section > Section currently stacks two query containers that nothing queries — after this change, zero.

A named container is not an alternative. container-name does not exclude an element from unnamed @container queries; it only adds a way to target it explicitly. Naming would change neither query resolution nor the containment, so it fixes nothing here.

Side effects of container-type that were checked

Probed directly in Chromium 149, WebKit 26.5 and Firefox 151 (Playwright, no repo code). All three engines agreed on every result below.

1. Intrinsic inline contribution — real, and load-bearing. Inline-size containment zeroes it. In a 400px grid with grid-template-columns: 1fr 1fr and unbreakable content in the first item:

track widths
container-type: inline-size 200 / 200
min-width: 0 200 / 200
neither 693 / 8 (blown out)

ColumnLayout uses plain Nfr tracks, and Nfr = minmax(auto, Nfr) with a content-based auto minimum, so this mattered. Hence min-width: 0 on .section. (LayoutCard already carries min-width: 0 for the same reason.)

Where min-width: 0 is not equivalent, and why it cannot matter. min-width: 0 zeroes only the minimum; containment zeroed the max-content contribution too. Probed every context where that distinction shows:

context contained min-width: 0 verdict
grid auto track 400 400 equivalent
flex row, definite width 200 200 equivalent
width: fit-content parent 0 785 differs
display: inline-block parent 0 785 differs
table cell 0 785 differs

The three that differ are all shrink-to-fit contexts, and in every one containment collapses the parent to 0 width. A Section in such a context would be invisible today, so no such layout exists in Flow — and if one ever appears, min-width: 0 is the correct behaviour, not a regression. Confirmed independently against the source: every auto / max-content / fit-content grid or shrink-to-fit rule in the repo is on a leaf component (Radio, RadioButton, LabeledValue, ProgressBar, Segment, NumberField, Button, ComboBox, Badge, Link, …) that cannot contain a Section; the only grid a Section is ever an item of is ColumnLayout, whose getColumns emits Nfr exclusively. Modal sizes its dialog from width: inherit plus a max-width token — definite, content-independent.

2. Containing block for absolutely positioned descendants — already gone. An position: absolute; left: 0 child of a container-type element sitting at left: 50px lands at left: 0, i.e. against the outer positioned ancestor, in all three engines. Chrome 129 aligned this to spec; nothing in the repo could depend on it, because it would already be broken.

3. Stacking context — already gone. A z-index: 5 descendant of a container-type element still paints over a z-index: 2 sibling of that element in all three engines. Not trapped, so not a stacking context.

4. Block-axis containment — never applied. inline-size never contained the block axis, so nothing about Section's height behaviour changes.

Verification

Command Result
pnpm nx test:unit components 253 passed (34 files)
pnpm nx test:compile components pass
pnpm nx test:compile remote-react-components pass
pnpm nx test:browser components --browser.name=webkit 262 passed (38 files)
pnpm nx test:browser remote-react-components --browser.name=webkit 10 passed
pnpm lint 0 errors (118 pre-existing warnings)
pnpm nx test:visual remote-react-components --browser.name=webkit (local, darwin) 358 / 360 passed

build:scss-types produces no diff — no class name changed.

CI visual suite — full run, both browsers (run-visual-tests, run 33610994607)

The label run renders every file in both browsers, so it covers webkit-linux (light) and firefox-linux (dark), Local and Remote. Result: 5 of 6 shards fully green, and the only 4 failures in the whole suite are

Section growing inside a LayoutCard (Local)   [webkit]  No existing reference screenshot found.
Section growing inside a LayoutCard (Remote)  [webkit]  No existing reference screenshot found.
Section growing inside a LayoutCard (Local)   [firefox] No existing reference screenshot found.
Section growing inside a LayoutCard (Remote)  [firefox] No existing reference screenshot found.

Exactly the four references the new scenario does not have yet. Zero pixels (ratio …) differ, zero does not match, zero Could not capture anywhere in that run. No existing baseline moved, in either browser or either theme.

CI visual suite — Run tests shards (run 33610980760, webkit-linux)

Same conclusion, plus one unrelated infrastructure failure worth naming:

shard files result
1 21 20 passed, 1 failed — 86/88 tests. Only failure: No existing reference screenshot found. for the new scenario (Local + Remote). Includes ColumnLayout — passed, and Section's two pre-existing baselines — passed.
2 21 all passed
3 21 first attempt: all 21 files failed with one reason, Could not capture a stable screenshot within 5000ms. (111 tests) — a whole-shard capture collapse, the known shard-stability issue. Re-run: all 21 passed, no diffs.
4 21 all passed — includes LayoutCard, Table, SegmentedControl

Shard 3's first attempt was never this change: it is a capture-stability timeout, not a mismatch; it hit every file in the shard uniformly, including AlertIcon, Color, Icon and Avatar, which contain no Section at all; not one pixel comparison completed; and removing containment reduces layout work rather than preventing the page from settling. Re-running the same commit turned it green, which settles it.

Every load-bearing candidate is verified green against the Linux baselines: ColumnLayout (the only grid a Section is an item of), LayoutCard (the clipping ancestor from the report), Table, SegmentedControl, List, and Section itself.

The one local failure, Initials, is not a finding either: reported as 1 pixels (ratio 0.01) differ, and opening the diff shows 296 raw pixels differing entirely inside the 😄 glyph's box (x 81–90, y 34–45) with per-channel deltas of 1–15 and no geometry change — emoji rasterisation noise under CPU load. It passes in isolation both with and without this change, and that test renders Flex > Initials with no Section anywhere. It passed in CI.

The section above documents the state before a0b466a; the baselines it calls missing are committed since.

New visual scenario

Section growing inside a LayoutCard reproduces the reporter's structure — LayoutCard > Section > Section > Switch where the switch reveals an Alert plus a ColumnLayout of fields. Verified locally in both environments: the card grows to fit, nothing is clipped, and Local and Remote agree pixel-for-pixel against the same reference.

It cannot reproduce the bug itself — that is Blink-specific and Chrome-version-specific, and this suite runs WebKit and Firefox. It guards the height path so a future regression shows up as a diff.

Its baselines were created by the update-screenshots run on a0b466a and are committed. On the current head the whole suite is green in both paths — the visual gate (4 shards) and a verify-only run-visual-tests (6 shards) — so no baseline differs any more and nothing is left to update.

Still open from the issue

Steps 2 and 3 of the issue's suggested next steps: have the reporter verify in the affected Chrome, and optionally file a minimal repro on issues.chromium.org.

List — the same dead containment

.list declared container-type: inline-size with nothing resolving against it either: its own Header declares .header, and ListItemView @used the containerBreakpointSizes mixin without ever including it (that dead @use is gone too). A List sits inside a Section in most real layouts, so it carried the same #2655 exposure.

min-width: 0 is load-bearing here as well. Track widths (list / sibling) for a List with unbreakable content in a 400px two-track grid, measured in webkit:

.list list view tiles view table view
container-type: inline-size 196 / 196 196 / 196 196 / 196
min-width: 0 196 / 196 196 / 196 196 / 196
neither 479 / 49 230 / 162 704 / 49

.items clipping its overflow does not cover this on its own — those numbers are with the clipping in place. The new test in List.browser.test.tsx asserts the equality across all three view modes. It waits for the async items first: an empty list cannot blow out a track and measures 196/196 either way, which is how a first (wrong) measurement of this made the containment look free.

fixes #2655

🤖 Generated with Claude Code

@mfal mfal self-assigned this Sep 2, 2026
@mfal mfal added the run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch label Sep 2, 2026
@github-actions github-actions Bot removed the run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch label Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for ./packages/components/

Status Category Percentage Covered / Total
🔵 Lines 78.69% 746 / 948
🔵 Statements 78.57% 763 / 971
🔵 Functions 80.09% 165 / 206
🔵 Branches 70.33% 377 / 536
File CoverageNo changed files found.
Generated in workflow #6677 for commit 4d17ade by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

✅ Visual Regression Tests Passed

All visual snapshots match the committed baselines.

Run details

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🚀 Preview Deployment

Preview environments are ready:

Type URL
docs pr-3082.docs.review.flow-components.de
storybook pr-3082.storybook.review.flow-components.de

Images:

  • docs: ghcr.io/mittwald/flow/docs:pr-3082
  • storybook: ghcr.io/mittwald/flow/storybook:pr-3082

@mfal mfal added the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 2, 2026
@github-actions github-actions Bot removed the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 2, 2026
`Section` declared `container-type: inline-size` for a `@container` query on
its own header. That query left with the `Header` component. Nothing queries a
section any more — every `@container` in the repo resolves against a container
the querying component declares itself.

The containment was therefore pure cost, and the cost is a Blink
layout-invalidation bug (#2655): under a `container-type` element an async
descendant size change can fail to invalidate the container's block-size
layout, so it keeps the stale short height and the ancestor clips the revealed
content until a later style recalc — a hover anywhere on the page — forces the
flush. Sections nest, so `Section > Section` stacked two containers around the
toggled content.

`min-width: 0` keeps the one load-bearing side effect: inline-size containment
zeroed the intrinsic inline contribution, which is what stops a section with
unbreakable content from blowing out a `ColumnLayout` grid track. Measured in
Chromium 149 / WebKit 26.5 / Firefox 151, a 400px two-track grid gives 200/200
with either containment or `min-width: 0`, and 693/8 with neither. The
containing-block and stacking-context side effects are already gone in all
three engines (Chrome 129 aligned to spec), so nothing could depend on them.

Adds a visual scenario for the reporter's structure — a `Switch` inside nested
sections inside a `LayoutCard` revealing taller content.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mfal mfal added update-screenshots Label a PR to update the screenshots used for visual regression testing run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch labels Sep 3, 2026
@github-actions github-actions Bot removed run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch update-screenshots Label a PR to update the screenshots used for visual regression testing labels Sep 3, 2026
@mfal mfal added the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
@github-actions github-actions Bot removed the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
@mfal mfal added the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
@github-actions github-actions Bot removed the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
@mfal mfal added the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
@github-actions github-actions Bot removed the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
@mfal mfal added the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
@github-actions github-actions Bot removed the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
@mfal mfal added the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
@github-actions github-actions Bot removed the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
@mfal mfal added the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
@github-actions github-actions Bot removed the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
@mfal mfal added the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
@github-actions github-actions Bot removed the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
@mfal mfal added the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
@github-actions github-actions Bot removed the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 3, 2026
Co-authored-by: mfal <4696750+mfal@users.noreply.github.com>
@mfal
mfal marked this pull request as ready for review September 3, 2026 12:12
@mfal
mfal requested a review from a team September 3, 2026 12:12
mfal and others added 3 commits September 4, 2026 09:17
The decision and the measurements behind it live in the PR body and the
commit that made the change. Repeating them as a 24-line block above
`.section` only adds a second copy to keep in sync.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`Section.module.scss:5` was the `container-type` line this branch removes,
so the pattern cited an example that no longer exists. `ColumnLayout`'s
`.columnLayoutContainer` is the canonical case, and the convention every
`@container` in the repo follows is that the querying component declares
its own container.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Same dead containment `Section` loses in this branch: `.list` declared
`container-type: inline-size`, but every `@container` in the repo resolves
against a container the querying component declares itself — `ColumnLayout`
on `.columnLayoutContainer`, `List`'s own `Header` on `.header`,
`SegmentedControl` on `.segmentedControl`. Nothing lands on `.list`, so the
containment only bought exposure to the Blink layout-invalidation bug from
#2655, and a `List` nests inside a `Section` in most real layouts.

`min-width: 0` keeps the one load-bearing side effect. Measured in webkit
with a `List` in a 400px two-track grid, tracks (list/sibling):

| .list             | list view | tiles view | table view |
| ----------------- | --------- | ---------- | ---------- |
| `container-type`  | 196/196   | 196/196    | 196/196    |
| `min-width: 0`    | 196/196   | 196/196    | 196/196    |
| neither           | 479/49    | 230/162    | 704/49     |

`.items` clipping its overflow is not enough on its own — the numbers above
are with that in place. The new test asserts the equality in all three view
modes; it has to wait for the async items, because an empty list cannot blow
out a track and measures 196/196 either way.

Also drops ListItemView's `@use` of the container-breakpoint mixin, which it
never included.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mfal mfal added the run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch label Sep 4, 2026
@github-actions github-actions Bot removed the run-visual-tests Runs the full visual regression suite against the existing baselines and fails the check on mismatch label Sep 4, 2026
@mfal mfal changed the title fix(Section): stop declaring a query container nothing queries fix(components): stop declaring query containers nothing queries Sep 4, 2026
@mfal mfal added update-screenshots Label a PR to update the screenshots used for visual regression testing and removed update-screenshots Label a PR to update the screenshots used for visual regression testing labels Sep 4, 2026
mfal and others added 2 commits September 4, 2026 10:48
…-type-2655

# Conflicts:
#	packages/components/src/components/List/List.browser.test.tsx
The branch already moved the container-type rationale out of the code
(d4b2775, 272741d) and left this copy behind — the `minmax(auto, Nfr)`
mechanism and the measured 196/196 vs 479/49 numbers belong in the PR body,
not in a test file.

What stays is what a reader of the test needs: which declaration the list
depends on, and what breaks without it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mfal mfal added update-screenshots Label a PR to update the screenshots used for visual regression testing and removed update-screenshots Label a PR to update the screenshots used for visual regression testing labels Sep 4, 2026
@github-actions github-actions Bot removed the update-screenshots Label a PR to update the screenshots used for visual regression testing label Sep 4, 2026
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.

Section/LayoutCard height not updating in Chrome when content changes (clipped content)

1 participant