Skip to main content

Tree title overflow → on-demand measurement (DES-132)

Version: 0.4.0 · Type: ✨ Feature

Related issue: DES-132 (sub-task under the DES-75 Tree parent issue)

Problem

The left-side folder list in web4 (FolderTreeAdapter) showed noticeable lag once the folder count reached the hundreds. The DOM rows themselves are not complex, so the root cause is not the number of nodes but the fact that on every TreeNode mount, useTitleOverflow synchronously runs measureTitleOverflow once:

titleEl.clientWidth // read layout
host.appendChild(clone) // write DOM
clone.offsetWidth // read layout → forced synchronous reflow
clone.remove() // write DOM

When hundreds or thousands of nodes mount at once, this alternating "write → read → write → read" triggers an equal number of forced synchronous reflows (layout thrashing). The browser cannot batch them, which is the main source of the lag. On top of that, each node keeps a permanent ResizeObserver, which further amplifies the cost.

Changed Files

  • src/components/Tree/Tree.tsx
  • src/components/Tree/__tests__/Tree.test.tsx
  • src/components/Tree/__tests__/Tree.ct.spec.tsx

Changes

  • useTitleOverflow changed from "measure on mount + a permanent per-node ResizeObserver" to on-demand measurement: it now returns [overflowed, measure], and measure only runs once when the pointer enters / the row is focused. If the same title has already been measured, it is skipped. The per-node reflow during mount drops to 0.
  • The TreeNode row container adds onPointerEnter / onFocus to trigger measure (onFocus preserves keyboard accessibility, so a focused row can also trigger the tooltip wrapping).
  • The tooltip is only needed on hover / focus anyway, so deferring the measurement to that moment is imperceptible to the user.

Behavior Change

(contract update)

  • An overflowing title is no longer immediately wrapped in a Tooltip trigger on mount; it is wrapped after the pointer first enters / the row is first focused.
  • After removing the ResizeObserver, container width changes (such as dragging the sidebar) no longer trigger an automatic re-measure; the stale state refreshes the next time the pointer enters / the row is focused.
  • Tests updated accordingly: the tooltip assertions in Tree.test.tsx / Tree.ct.spec.tsx now first trigger pointer enter / hover before asserting data-state.

Notes

This is a per-node optimization that reduces the fixed cost per node; the total node count is still linear. It significantly relieves the hundreds-to-one-or-two-thousand node scenarios. If an extreme scenario with several thousand nodes appears later, a virtual list (flatten + viewport rendering) will be needed to fully solve it.