Skip to content

fix(flexbox): keep a hidden node at the position it was laid out - #127

Merged
DouweBos merged 1 commit into
mainfrom
douwe/flexbox-hidden-node-keeps-position
Sep 23, 2026
Merged

DouweBos merged 1 commit into
mainfrom
douwe/flexbox-hidden-node-keeps-position

Conversation

@DouweBos

Copy link
Copy Markdown
Contributor

Context

Found from a Plex TV smoke-test regression on Lightning. Two Maestro flows started failing on assertVisible: id: primary-navigation-account, focused — the page object treats the presence of that element as "the nav drawer is expanded", and it was suddenly always present while the drawer was closed.

The app change that exposed it was innocent: the drawer now sets display: 'none' once its close animation finishes, instead of being drawn off screen every frame. The drawer is parked off screen by a translateX, and hiding it moved the whole subtree back to the origin. Nothing changed visually, but the testID mirror the renderer's inspector builds — which Maestro queries — reported the closed drawer as an on-screen element at 0,0.

Discussion

Yoga zeroes the computed box of a display: none node. _getUpdatedStyles emits that zeroed box like any other layout, and LightningManager._applyUpdates writes x/y unconditionally — only width/height are guarded against 0. So hiding a node is enough to move it, and any position it held from a translate is lost.

YogaManager already had the machinery for this: _getUpdatedStyles skips an element in _hiddenElements (unless processHiddenNodes is set), but nothing ever populated that set. This fills it in — applyStyle adds the element when the node's display resolves to DISPLAY_NONE and removes it otherwise, and removeNode clears it. A hidden node stops emitting updates and keeps the geometry it had; showing it again re-lays it out as before.

Reading getDisplay() from the node rather than the incoming style keeps the resets/partial-push semantics of applyReactPropsToYoga authoritative — a full push that omits display resets it to flex, and that is reflected. One FFI read per style application, not per frame.

Steps to test

Two unit tests in YogaManager.displayToggle.test.ts, red before the change for the right reason (expected { x: 0 } to match { x: -452 }):

  1. A node translated off screen keeps x: -452 when it is hidden.
  2. A hidden node is laid out again when it is shown.

Verified live against the Plex Lightning app on the emulator, reading the inspector mirror over CDP. With the drawer closed after a real open → close cycle, primary-navigation-account composes to x: -452, right: -130 (entirely off screen) with this change, versus x: 0, width: 322 without it. The drawer itself opens and closes unchanged.

plugin-flexbox suite: 211/211 pass, and the repo unit tests are green.

Yoga zeroes the computed box of a display:none node. Emitting that layout
dragged the node and its subtree to the origin, so an element parked off
screen by a translate read as on screen to anything walking the scene
graph — the Plex TV drawer's nav items showed up in the testID mirror at
0,0 while the drawer was closed.

Populates the _hiddenElements set _getUpdatedStyles already consults, so
a hidden node stops emitting updates until it is shown again.
@DouweBos
DouweBos requested a review from RuudBurger September 23, 2026 14:11
@DouweBos
DouweBos marked this pull request as ready for review September 23, 2026 14:11
@DouweBos
DouweBos merged commit dd64419 into main Sep 23, 2026
2 checks passed
@DouweBos
DouweBos deleted the douwe/flexbox-hidden-node-keeps-position branch September 23, 2026 17:05
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.

2 participants