Skip to content

Repeater: collapse empty field wrappers via padding, not display:none (fixes Safari zero-height rows) - #92

Open
juliacanzani wants to merge 1 commit into
mainfrom
fix/webkit-has-empty-repeater-rows
Open

Repeater: collapse empty field wrappers via padding, not display:none (fixes Safari zero-height rows)#92
juliacanzani wants to merge 1 commit into
mainfrom
fix/webkit-has-empty-repeater-rows

Conversation

@juliacanzani

@juliacanzani juliacanzani commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

(not needed if #93 is approved)

Symptom

Opening a row in a layout: advanced repeater in Safari expands to an empty zero-height strip — the row's fields are in the DOM but have no layout boxes — and stays that way for many seconds (sometimes indefinitely), until some unrelated global style invalidation happens to occur. Reported from Tangible SearchSync's index-settings screen ("the repeater takes several seconds to expand"), reproduced deterministically in Playwright WebKit; Chromium is unaffected.

Root cause

.tf-repeater-advanced-item-field:has(> div[data-overlay-container="true"]:empty) {
  display: none;
}

WebKit evaluates :has(> :empty) for a freshly inserted subtree before the subtree's children are attached, so at insertion the selector matches every field wrapper in the new row — and WebKit then fails to re-evaluate once the children exist. The stale display: none persists in the used style. Debugging note for posterity: getComputedStyle() re-resolves fresh and reports display: block while the render box simply doesn't exist, and element.matches(...) returns the correct (non-matching) result — everything reads healthy while nothing paints.

Evidence chain (all Playwright WebKit against a live WP admin page):

  • Row content mounts with offsetHeight/offsetWidth of 0 at every level down to bare <input>s, despite computed display: block/flex and min-height: 32px.
  • No inline styles and no matching height rules; scrollHeight 0.
  • Class toggles and DOM pokes on the affected elements do not heal; any stylesheet-level mutation (deleteRule/insertRule/textContent) heals instantly and permanently.
  • Deleting only this rule before expanding → row renders in 16ms. That's the whole bug.

Fix

padding: 0 instead of display: none, in both repeater layouts using the pattern (advanced, block). The rule's intent survives — an empty wrapper's only footprint is its padding, so zeroing it collapses the wrapper to 0×0. And the WebKit stale-match now degrades gracefully: visible fields briefly render without wrapper padding (self-corrects on the next recalculation) instead of being invisible for seconds.

Verified in both engines on the reporting screen (8-row and 120-row repeaters):

before after
WebKit row expand 0-height strip, ≥30s visible in ~40ms, empty wrappers 0px
Chromium row expand fine fine (unchanged behavior)

Not changed, worth a look

assets/src/components/base/tab/index.scss has the same pattern (&:has(> .tf-tab-row-field > div[class*='tf-context-']:empty) { display: none }). A padding swap is not equivalent there — the rule hides a whole labelled row, so zero padding would still leave the label visible. It carries the same WebKit stale-match risk for tab rows mounted after initial render; flagging rather than changing speculatively.

Source-only change — needs a build before release.

🤖 Generated with Claude Code

…:none — unbreaks Safari

WebKit evaluates `:has(> [data-overlay-container]:empty)` for a freshly
inserted subtree BEFORE the subtree's children are attached, so on insertion
the selector matches EVERY field wrapper — and WebKit then fails to
re-evaluate once children exist. The stale `display: none` persists in the
used style (getComputedStyle reports the corrected value, which makes this
miserable to debug) until any later stylesheet mutation forces a global
recalculation.

Observable result: opening a repeater-advanced row in Safari expanded to an
empty zero-height strip, with all fields present in the DOM but boxless,
for many seconds — until some unrelated style invalidation happened to occur.

`padding: 0` keeps the intent (an empty wrapper's only footprint is its
padding) with a graceful stale-case: visible fields briefly render without
wrapper padding instead of being invisible, self-correcting on the next
recalculation. Applied to both repeater layouts using the pattern (advanced,
block).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliacanzani
juliacanzani marked this pull request as ready for review August 25, 2026 01:46
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.

1 participant