Skip to content

[WC-3564]: fix v2 tree node expand icon/spinner bugs - #2425

Open
yordan-st wants to merge 4 commits into
mainfrom
fix/WC-3564_tree-node-stuck-spinner-microflow
Open

yordan-st wants to merge 4 commits into
mainfrom
fix/WC-3564_tree-node-stuck-spinner-microflow

Conversation

@yordan-st

Copy link
Copy Markdown
Contributor

Pull request type

Bug fix (non-breaking change which fixes an issue)


Description

Tree Node v2 stored an expand-icon "loading" state per node and resolved it via a broken heuristic (a node's id reappearing in some later datasource delivery), which a microflow datasource breaks in two ways: a permanently stuck loading spinner when "Start expanded" is Yes, and an unrelated node silently and permanently losing its expand icon when a sibling is expanded (WC-3564). The spinner is now a pure render-time decision driven by the datasource's own status, never stored per node.

Manual verification of that fix surfaced two further pre-existing bugs in the same "preload one level ahead" mechanism, fixed in the same PR since they were found and understood during the same pass: a node's first expand didn't preload its own children's children (needed a collapse+re-expand to reveal a deeper tier), and the automatic root-expansion path for "Start expanded" = Yes only went one level deep instead of cascading to the tree's real depth (every level defaults to expanded in that mode, not just roots).

What should be covered while testing?

  • Tree Node v2, microflow datasource, "Start expanded" = Yes → tree renders fully expanded immediately, all the way to the data's real depth, no stuck spinner
  • Tree Node v2, "Start expanded" = No, expand a node, then expand an unrelated sibling → first node's expand icon stays intact, still clickable, reveals its own children correctly
  • Expand a node once → its children's own expand icons are correct immediately (no collapse+re-expand needed to reveal a deeper tier)
  • With "Start expanded" = Yes, deeper tiers (3rd, 4th, ...) appear automatically on load, not requiring a manual collapse/re-expand anywhere
  • DevTools console — no errors during any of the above
  • v1 widget (no parentAssociation configured) — spot-check unaffected, this change only touches v2 code paths

No XML changes; this is a bug fix restoring intended behavior, not a new feature — no docs PR needed. Changelog entries already added under [Unreleased].

Ticket: WC-3564

@yordan-st
yordan-st marked this pull request as ready for review September 15, 2026 14:46
@yordan-st
yordan-st requested a review from a team as a code owner September 15, 2026 14:46
@yordan-st
yordan-st force-pushed the fix/WC-3564_tree-node-stuck-spinner-microflow branch from 1ab005e to 44f3740 Compare September 15, 2026 14:54
@github-actions

Copy link
Copy Markdown
Contributor

AI Code Review

⚠️ Approved with suggestions — low-severity items only, safe to merge


What was reviewed

File Change
src/components/v2/TreeNode.tsx Spinner logic moved to pure render-time decision; isDatasourceLoading prop threaded through the recursive renderer
src/components/v2/hooks/useIncrementalTreeData.ts Removed per-node LOADING state; nodes now only ever start as EXPANDED or COLLAPSED_WITH_JS
src/components/v2/hooks/useInfiniteTreeNode.ts Unbounded cascade for startExpanded=true; fixed premature round1/round2 lock-in; fixed missing grandchild preload on first expand
src/components/v2/__tests__/TreeNodeV2.spec.tsx New loading-state regression suite (WC-3564 bugs 1 & 2)
src/components/v2/hooks/__tests__/useIncrementalTreeData.spec.ts Hook-level coverage for the removed LOADING state and sibling isolation
src/components/v2/hooks/__tests__/useInfiniteTreeNode.spec.ts Full cascade / bounded-preload tests for both startExpanded modes
CHANGELOG.md Four [Unreleased] entries, one per fixed bug

Skipped (out of scope): dist/, CONTEXT.md, openspec/ directory, pnpm-lock.yaml


Findings

⚠️ Low — TreeNodeV2.spec.tsx uses hand-rolled makeListValue instead of repo builders

File: packages/pluggableWidgets/tree-node-web/src/components/v2/__tests__/TreeNodeV2.spec.tsx lines 25–41, 43–64
Note: The test file constructs ListValue, ListExpressionValue, and ListReferenceValue by hand. The repo's @mendix/widget-plugin-test-utils provides ListValueBuilder, listReference, dynamic.*, etc., which are the expected pattern (used correctly in useIncrementalTreeData.spec.ts and useInfiniteTreeNode.spec.ts). The hand-rolled mocks aren't wrong, but they skip status edge cases (e.g. ValueStatus.Loading, ValueStatus.Unavailable) that the builders make trivial to inject. Worth aligning in a follow-up to keep the two test files consistent.


⚠️ Low — showSpinner is also true when the datasource is loading but the node is already expanded

File: packages/pluggableWidgets/tree-node-web/src/components/v2/TreeNode.tsx line 24
Note: The current logic is const showSpinner = !hasChildren && isDatasourceLoading. When isDatasourceLoading is true and a node has been expanded (its children are present, so hasChildren is true), the spinner is correctly suppressed. However, a collapsed node with known children will show a spinner while the datasource is loading more items for other nodes, because !hasChildren is false — actually, that case is fine. The subtle risk is the opposite: a node that was expanded and then the user collapses it — hasChildren is still true, so no spinner, which is also correct. The logic is sound; this is just noting that the relationship between isDatasourceLoading (which is the whole datasource's status, not a per-node status) and a node's own loading state is implicit. A brief inline note on line 24 explaining "whole-datasource loading" would help the next reader, but this is genuinely non-blocking.


Positives

  • The core fix — removing per-node LOADING state entirely and deriving spinner visibility at render time from datasource.status — is exactly the right approach. Stored loading state was the root cause of all three bugs; removing it eliminates the class of problem, not just the current instances.
  • The startExpanded=true cascade in useInfiniteTreeNode is content-gated (checks whether addedAny is true) rather than fire-count-gated, which is the correct self-terminating design and directly addresses the transient-empty-delivery regression.
  • round1DoneRef / round2DoneRef being content-gated (locked in only once loadedParentsByIdRef.current.size > 0) is a precise and minimal fix for the premature lock-in bug.
  • useIncrementalTreeData.spec.ts uses @mendix/widget-plugin-test-utils builders (listReference, dynamic.*) consistently — the right pattern.
  • The useInfiniteTreeNode.spec.ts cascade test is particularly thorough: it simulates transient empty deliveries, unchanged re-deliveries, and per-level progression in sequence, all in one test, which exactly mirrors the scenario that was previously broken.
  • Changelog entries are user-facing and describe behavior changes (not implementation details), matching the project convention.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant