Skip to content

fix(joint-react): re-measure auto-sized elements after external size writes - #3485

Draft
samuelgja wants to merge 1 commit into
clientIO:masterfrom
samuelgja:fix/stale-auto-size-write
Draft

fix(joint-react): re-measure auto-sized elements after external size writes#3485
samuelgja wants to merge 1 commit into
clientIO:masterfrom
samuelgja:fix/stale-auto-size-write

Conversation

@samuelgja

@samuelgja samuelgja commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Description

Hardening of the auto-size measurement pipeline: writes from outside the pipeline (controlled-mode sync, cell.resize()) and the ResizeObserver measurements no longer leave the model silently diverged from the DOM.

Three defects fixed:

  1. The dedup memory swallowed the re-measurement. After an external size write, a delivery repeating the last-measured numbers was dropped by the lastWidth/lastHeight guard — the model kept the external size while the DOM showed the measured one, permanently.
  2. No re-measurement was triggered at all. ResizeObserver does not re-fire when the DOM itself did not change, so the documented behavior ("the measured content size overrides an external resize") did not actually happen until the content changed by more than the epsilon.
  3. A measurement echo could clobber a newer write. The first delivery after an external write can still be measured from the pre-write layout (the DOM has not re-rendered yet). Writing it back overwrote the newer size.

The fix: GraphStore listens to every non-autoSize change:size and invalidates the cell's measurement state — the dedup memory is cleared and the active node re-observed (unobserve() resets the observer's last-reported size, so observe() forces a fresh delivery; observe() alone is a spec no-op for an already-observed target). The invalidation also records the size the write replaced: exactly one following delivery matching it is treated as a pre-re-render echo — dropped, with the node re-observed once more so the settled layout gets its own delivery. Re-activating a node from the multi-useMeasureElement stack also starts with clean measurement memory, closing the same dedup hole in the fallback path.

The pipeline's own writes are marked with the autoSize option and skipped, so there is no feedback loop; for unobserved (useModelGeometry) elements the listener is an O(1) no-op.

Motivation and Context

Found while investigating a JointJS+ demo where rapid collapse/expand toggling desynced a group's size from its data. Note the scope honestly: the demo's element-orphaning itself turned out to be caused elsewhere (a spatial-index staleness in JointJS+, fixed separately there) — this PR does not claim to fix that. What it fixes are the measurement-pipeline defects above, which are real, deterministic, and reproducible in unit tests: without this change an element whose size is written externally can stay diverged from its rendered DOM indefinitely.

Tests (written first, failing before the fix): a graph-store integration test driving the real pipeline (measure → external resize → echo dropped → settled re-measurement wins), a re-observation mechanism test, an autoSize no-feedback-loop guard, echo one-shot semantics, and observer-level unit tests for invalidate. Full suite green.

…writes

An external size write (controlled-mode sync, cell.resize) on a measured
element could leave the model silently diverged from the DOM: the
measurement pipeline does not re-fire when the DOM did not change, and its
last-measurement dedup memory swallowed a delivery repeating the
pre-write numbers. Under rapid state toggling (e.g. collapse/expand of a
measured card) this left elements stuck at a stale measured size.

The graph store now invalidates the cell's measurement state on any
non-autoSize change:size: the dedup memory is cleared and the active node
is re-observed, which forces the ResizeObserver to deliver the current
layout so the measured size is re-asserted. Re-activating a node from the
measurement stack also starts with a clean memory, closing the same hole
in the multi-hook fallback path.
@samuelgja
samuelgja force-pushed the fix/stale-auto-size-write branch from 8fca355 to a0a1120 Compare August 26, 2026 04:18
@samuelgja
samuelgja marked this pull request as draft August 26, 2026 14:01
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