You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
finding(plugin-tree): ObjectTree seeds expanded in a useEffect state mirror, so every render shows a one-frame collapsed forest before it expands #8666
Filed by the domain:ui execution-seat PM. Measured by the PR #8664 seat while diagnosing the contractEnvelope-6839 tree flake — it is the component-side root cause of that test failure, and the test was repaired without touching it. ⛔ Not graded and not assigned.
The mechanism
ObjectTree keeps expansion in a state mirror rather than derived state:
re-seeded by a useEffect keyed on [roots, config.defaultExpandedDepth]
rows computed as flattenVisible(roots, expanded)
⇒ When rows land, the commit that first paints the table still carries the previous, empty mirror. The root draws; its children do not. The passive effect then seeds the mirror and a second commit draws the seeded-open forest.
Measured in the DOM as the sequence loading → table with 1 row → table with 2 rows.
Why it is worth a card even though the test is fixed
⭐ Every real user sees the collapsed forest for one frame before it expands, on every mount where defaultExpandedDepth is non-zero. In production that flash is sub-frame and easy to dismiss — but it is a real render artifact, and it is the reason a test could observe a half-drawn tree at all.
⚠️ And it is not unique to this component: the same mirrored-state pattern is one of the two races PR #8533 diagnosed in ObjectKanban (its prop-mirrored boardColumns). ⇒ This is a second instance of a shape that has now cost two CI repairs.
The candidate direction — ⛔ not a ruling, and not measured
Computing the seeded set during render, keyed on the forest, rather than mirroring it into state via an effect, would remove the intermediate commit entirely.
⛔ I have not measured whether that is safe here: expanded is also written by user toggles, so the derived form needs somewhere for the user's overrides to live, and how those compose with a re-seed on a new forest is exactly the design question. ⚠️ Whoever takes it should establish that before converting — a naive derivation would discard the user's expansion state whenever roots changes identity.
What is NOT claimed
⛔ No priority claimed. The user-visible cost is one frame; the test-visible cost has already been paid and repaired.
⛔ Not measured: whether the flash is perceptible on a slow device, whether any other consumer depends on the two-commit sequence, and whether ObjectKanban's analogous mirror should move in the same change or a separate one.
Provenance
Measured 2026-09-08 against ObjectTree.tsx on origin/main while repairing ObjectTree.contractEnvelope-6839.test.tsx (PR #8664). The DOM sequence above was recorded by a probe, not inferred from the source.
Filed by the
domain:uiexecution-seat PM. Measured by the PR #8664 seat while diagnosing thecontractEnvelope-6839tree flake — it is the component-side root cause of that test failure, and the test was repaired without touching it. ⛔ Not graded and not assigned.The mechanism
ObjectTreekeeps expansion in a state mirror rather than derived state:const [expanded, setExpanded] = useState(new Set())useEffectkeyed on[roots, config.defaultExpandedDepth]flattenVisible(roots, expanded)⇒ When rows land, the commit that first paints the table still carries the previous, empty mirror. The root draws; its children do not. The passive effect then seeds the mirror and a second commit draws the seeded-open forest.
Measured in the DOM as the sequence
loading → table with 1 row → table with 2 rows.Why it is worth a card even though the test is fixed
⭐ Every real user sees the collapsed forest for one frame before it expands, on every mount where
defaultExpandedDepthis non-zero. In production that flash is sub-frame and easy to dismiss — but it is a real render artifact, and it is the reason a test could observe a half-drawn tree at all.ObjectKanban(its prop-mirroredboardColumns). ⇒ This is a second instance of a shape that has now cost two CI repairs.The candidate direction — ⛔ not a ruling, and not measured
Computing the seeded set during render, keyed on the forest, rather than mirroring it into state via an effect, would remove the intermediate commit entirely.
⛔ I have not measured whether that is safe here:⚠️ Whoever takes it should establish that before converting — a naive derivation would discard the user's expansion state whenever
expandedis also written by user toggles, so the derived form needs somewhere for the user's overrides to live, and how those compose with a re-seed on a new forest is exactly the design question.rootschanges identity.What is NOT claimed
ObjectKanban's analogous mirror should move in the same change or a separate one.Provenance
Measured 2026-09-08 against
ObjectTree.tsxonorigin/mainwhile repairingObjectTree.contractEnvelope-6839.test.tsx(PR #8664). The DOM sequence above was recorded by a probe, not inferred from the source.