Skip to content

Tiled Gallery: fix infinite resize loop inside Row/Stack blocks#50016

Open
dhasilva wants to merge 5 commits into
trunkfrom
fix/tiled-gallery-loop
Open

Tiled Gallery: fix infinite resize loop inside Row/Stack blocks#50016
dhasilva wants to merge 5 commits into
trunkfrom
fix/tiled-gallery-loop

Conversation

@dhasilva

@dhasilva dhasilva commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Fixes #49564
Fixes JETPACK-1726

Proposed changes

  • Fix an infinite resize loop in the Tiled Gallery block when it is placed inside a Row or Stack block. The mosaic layout reserved two gutters per column gap (GUTTER_WIDTH * 2) while the DOM renders only one. Inside a content-sized flex container (Row/Stack) the block's width is driven by its own content, so each ResizeObserver pass laid the content out one gutter narrower than it measured — spiralling the gallery toward zero width and snapping back, forever. It now reserves a single gutter per gap, matching the rendered DOM (and every deprecated layout copy).
  • Add a defensive re-entrancy guard to the mosaic ResizeObserver: it tracks the width of the last layout pass and ignores sub-pixel changes (the signature of a feedback loop rather than a real resize). triggerResize() clears the tracked width so genuine content/layout changes still recompute.
  • Add unit tests for the gutter accounting and the resize-loop guard.

This is editor/front-end runtime layout only; save() output is unchanged for any given attributes, so no block deprecation is required and existing galleries continue to validate.

Related product discussion/links

Does this pull request change what data or activity we track or use?

No.

Testing instructions

  • Create a new post or page in the block editor.
  • Add a Row block (the bug also reproduces with a Stack block).
  • Inside it, insert a Tiled Gallery block and add several images.
    • Before: the gallery continuously shrinks until it disappears, then snaps back to full size and repeats, making it hard to edit.
    • After: the gallery renders at a stable size and stays put. Confirm you can select and edit images normally.
  • Resize the editor (e.g. toggle the settings sidebar): the gallery should adapt to the new width and settle, without looping.
  • Sanity-check a Tiled Gallery placed in normal (non-flex) content: it should still render and resize correctly in both the editor and on the front end.

Before:

Screenshare.-.2026-06-26.6_22_33.PM.mp4

After:

Screenshare.-.2026-06-26.6_58_25.PM.mp4

dhasilva and others added 3 commits June 26, 2026 18:45
The mosaic layout reserved two gutters per column gap
(GUTTER_WIDTH * 2) while the DOM renders only one. Inside a
content-sized flex container (Row/Stack) the block's width is
driven by its own content, so each ResizeObserver pass laid the
content out one gutter narrower than it measured — spiraling the
gallery toward zero width and snapping back, forever.

Reserve a single gutter per gap, matching the rendered DOM and
every deprecated layout copy. Add a unit test asserting the
laid-out columns plus their rendered gutters fill the measured
width.

Fixes JETPACK-1726.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Defense in depth on top of the gutter accounting fix: track the
width of the last layout pass and ignore sub-pixel changes, which
are the signature of a ResizeObserver feedback loop rather than a
real resize. triggerResize() clears the tracked width so genuine
content/layout changes always recompute.

Refs JETPACK-1726.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dhasilva dhasilva added the [Status] Needs Review This PR is ready for review. label Jun 26, 2026
@dhasilva dhasilva self-assigned this Jun 26, 2026
@dhasilva dhasilva added Bug When a feature is broken and / or not performing as intended [Block] Tiled Gallery [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ labels Jun 26, 2026
@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack), and enable the fix/tiled-gallery-loop branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack fix/tiled-gallery-loop

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!


Jetpack plugin:

The Jetpack plugin has different release cadences depending on the platform:

  • WordPress.com Simple releases happen as soon as you deploy your changes after merging this PR (PCYsg-Jjm-p2).
  • WoA releases happen weekly.
  • Releases to self-hosted sites happen monthly:
    • Scheduled release: July 7, 2026
    • Code freeze: July 6, 2026

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

@dhasilva dhasilva requested a review from coder-karen June 26, 2026 22:01
@dhasilva

Copy link
Copy Markdown
Contributor Author

@coder-karen Asking for your review as this was introduced in #43345 and you might see something that was missed here 🙇‍♂️

@jp-launch-control

jp-launch-control Bot commented Jun 26, 2026

Copy link
Copy Markdown

Code Coverage Summary

Coverage changed in 2 files.

File Coverage Δ% Δ Uncovered
projects/plugins/jetpack/extensions/blocks/tiled-gallery/layout/mosaic/index.js 69/85 (81.18%) 22.69% -6 💚
projects/plugins/jetpack/extensions/blocks/tiled-gallery/layout/mosaic/resize.js 40/40 (100.00%) 100.00% -40 💚

Full summary · PHP report · JS report

@enejb

enejb commented Jun 26, 2026

Copy link
Copy Markdown
Member

Firefox follow-up: gallery settles to a non-deterministic size inside Row/Stack

Testing this branch on a Jurassic Ninja site, a Tiled Gallery placed inside a Row/Stack with selfStretch: "fit" (content size) renders at a different size on each reload in Firefox — sometimes large, sometimes small — while Chrome is consistent. The infinite loop this PR targets is gone, but a related symptom from the same underlying cause remains.

Root cause

mosaic/index.js observes this.gallery.current (the gallery node itself) and lays the rows out to that same node's measured width. When the block is a content-sized flex item (selfStretch:"fit"), the gallery's width shrink-wraps its content, so we have a circular constraint:

  • flex sets gallery width = width of its content
  • the resize JS sets content width = the measured gallery width

Every width is a fixed point — the layout just preserves whatever width it measured first. So the final size is decided by whatever width the ResizeObserver happens to read on its first callback, which depends on image-decode / observer scheduling. Chrome times that consistently; Firefox jitters per reload → different locked-in size each time.

The lastWidth + RESIZE_THRESHOLD guard stops the spiral, but by freezing at that first (timing-dependent) measurement — which is why the size is now stable-per-load but random-across-loads.

Reproduction (headless Playwright, same post)

Both engines are internally stable but converge to different sizes for identical content, and Chrome's values overflow the ~620px container:

gallery Firefox Chrome (pre-fix)
in its own Row 620×438 767×531
in its own Row 620×274 956×414

Suggested fix

Drive the layout from a width that doesn't depend on the gallery's own content: anchor to the nearest flex container's available width (sized by the surrounding layout) when the gallery is a flex item, and fall back to the gallery's own width otherwise. The ResizeObserver becomes a pure trigger; the layout target is measured fresh and stably.

Prototype diff (against this branch): replaces contentRect.width with a getLayoutWidth() that walks up to the nearest display:flex|inline-flex ancestor and uses its clientWidth, and also observes that container so window/responsive resizes still fire. With it applied, all single-gallery-per-row instances settle deterministically at the container width (628px) across 6/6 reloads in Chrome — no overflow, no per-reload variance. Existing mosaic/resize tests still pass, plus added unit coverage for the anchoring and fallback paths.

Known limitation of the prototype

Anchoring to container.clientWidth gives each flex sibling the full container width, so two galleries in a single Row overflow (each claims the whole line). It's still better than pre-fix, but a complete fix should apportion the container width across flex siblings (account for the item's resolved main-size) rather than using the full container width for each. Single-gallery-per-row — the common case and the one in the report — is fully resolved.

Happy to push the prototype branch / open a follow-up PR if useful.

@enejb

enejb commented Jun 27, 2026

Copy link
Copy Markdown
Member

Update: multi-gallery-per-row case resolved

The earlier prototype's known limitation (multiple galleries in one Row each claiming the full container width and overflowing) is fixed by only anchoring to the container when the gallery is the sole flex item:

// Only anchor to the container when the gallery is the SOLE flex item. A lone
// content-sized item has no well-defined width (it is circularly defined by our
// own layout). With several flex items the browser already distributes width
// across them, so the gallery's own measured width is the correct per-item share.
if ( container && 1 === container.children.length ) {
    return container.clientWidth;
}
return this.gallery.current ? this.gallery.current.clientWidth : 0;

The single-item case had selfStretch:"fit" (non-deterministic); the two-in-a-row galleries have no selfStretch and rely on the browser's flex distribution — which the original this.gallery.current measurement already captured correctly. The guard keeps that path for multi-item rows and only overrides the lone-item case.

Verified on both Chrome and Firefox against a post with single- and double-gallery Rows:

layout gallery width container flex children overflow
single in Row 620 620 1 0
two in Row 300 + 300 620 2 0

Single galleries fill the container deterministically; two-in-a-row each take ~half and sum to the container width with no overflow. Existing tiled-gallery tests pass (17/17) including added coverage for the lone-item, multi-item, and no-flex-container paths.

@enejb

enejb commented Jun 27, 2026

Copy link
Copy Markdown
Member

Pushed be79ab8 with the deterministic-sizing fix on top of this branch.

What it does (mosaic/index.js): the mosaic now lays out against a width that doesn't depend on its own content (which is circularly defined when the gallery is a content-sized flex item, so it settled to a different value on each reload in Firefox — and, with several galleries in a row, in Chrome too):

  • Lone flex item → anchor to the flex container's width.
  • Several flex items in one row → give each an equal share of the container width (minus the row gap), so two galleries are half each and don't overflow.
  • Outside a flex container → unchanged (the gallery's own width).

The ResizeObserver is now just a trigger; it also observes the container so window/responsive resizes still fire.

Verified on a post with single- and double-gallery Rows + a Stack, 8/8 reloads in Chrome: every gallery distinctW=1 (fully stable) — single galleries at the container width (628), paired galleries at 308 each with no overflow. Tiled-gallery JS tests pass (17/17), including added coverage for the lone-item, equal-share, and no-flex-container paths.

(Headless Firefox couldn't be used to confirm reload-stability because the block intermittently renders as an unsupported-block placeholder in automation — a registration race unrelated to this change — but the new width is content-independent, so the result is browser-agnostic.)

@dhasilva

Copy link
Copy Markdown
Contributor Author

Thanks for the fix, @enejb!

@enejb enejb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This works as expected for me now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Block] Tiled Gallery Bug When a feature is broken and / or not performing as intended [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Status] Needs Review This PR is ready for review. [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tiled Gallery keeps resizing in a loop inside Row/Stack in block editor

2 participants