LearnNewsExamplesServices
Frontmatter
id9430
titleTreeModel: Introduce childCount to decouple isLeaf state from emptiness
stateClosed
labels
enhancementaiarchitecturecore
assigneestobiu
createdAtMar 10, 2026, 2:25 PM
updatedAtMar 10, 2026, 2:34 PM
githubUrlhttps://github.com/neomjs/neo/issues/9430
authortobiu
commentsCount1
parentIssue9404
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMar 10, 2026, 2:34 PM

TreeModel: Introduce childCount to decouple isLeaf state from emptiness

Closed v12.1.0 enhancementaiarchitecturecore
tobiu
tobiu commented on Mar 10, 2026, 2:25 PM

Goal

Introduce a childCount field to TreeModel to decouple a node's intrinsic type (isLeaf) from its current structural state (empty vs populated) and ensure O(1) read access for UI components.

Context

Currently, isLeaf is conceptually overloaded. If a folder (branch node) loses its last child via drag-and-drop or deletion, naively reverting it to isLeaf: true would cause the UI to render it as a file, potentially breaking drag-and-drop logic. A folder should remain isLeaf: false forever, even when empty. However, the UI still needs to know if a branch node is empty so it can hide or disable the expand/collapse arrow.

We choose childCount over a boolean hasChildren because, while it requires slightly more frequent mutations (O(1) updates on the parent during TreeStore.splice()), it provides maximum data availability for the grid.Row hot-path. Developers can render badges like "Folder (5 items)" directly from record.childCount without expensive store.get() or #childrenMap lookups during 120fps virtual scrolling.

Acceptance Criteria

  • Add childCount (Integer, default 0) to Neo.data.TreeModel.
  • Update TreeStore.splice() (likely within updateSiblingStats or the mutation loops) to dynamically calculate and mutate childCount on parent nodes during additions, moves, and removals.
  • Ensure "Turbo Mode" (lazy instantiation) supports childCount directly on raw data objects.
  • Add a Playwright unit test verifying childCount updates correctly when children are added or removed, while the parent's isLeaf state remains unchanged.
tobiu added the enhancement label on Mar 10, 2026, 2:25 PM
tobiu added the ai label on Mar 10, 2026, 2:25 PM
tobiu added the architecture label on Mar 10, 2026, 2:25 PM
tobiu added the core label on Mar 10, 2026, 2:25 PM
tobiu added parent issue #9404 on Mar 10, 2026, 2:25 PM
tobiu referenced in commit 6bc5aa9 - "feat(data): Introduce childCount to TreeModel to decouple isLeaf state (#9430)" on Mar 10, 2026, 2:33 PM
tobiu
tobiu Mar 10, 2026, 2:34 PM

Input from Gemini 3.1 Pro:

✦ I have successfully implemented childCount on TreeModel and updated the TreeStore mutation logic.

Implementation Details:

  1. Model: Added childCount (Integer, default 0) to Neo.data.TreeModel.
  2. Mutation Logic: Updated TreeStore.updateSiblingStats(parentId). Since this method is already called during every structural mutation (add, remove, move) and already iterates over all children to update ARIA sibling stats, I hooked the childCount calculation into this exact loop. This ensures we update the parent's childCount (and fire recordChange events) optimally with O(1) overhead on an existing O(N) loop.
  3. Soft Hydration: Updated the initialization loop in TreeStore.splice() to inject childCount: 0 into raw data objects if it's missing, ensuring full compatibility with "Turbo Mode".

Unit Tests Added: I added a new test block to test/playwright/unit/data/TreeStore.spec.mjs:

  • childCount should accurately reflect the number of children and decouple from isLeaf: Verifies that adding children increases childCount, and removing all children drops childCount to 0 while keeping isLeaf: false.
  • Updated the existing Turbo Mode test to verify childCount is accurately calculated for raw objects during initial ingest.

All tests are passing. I have committed the changes (feat(data): Introduce childCount to TreeModel to decouple isLeaf state (#9430)) and will close this issue.

tobiu assigned to @tobiu on Mar 10, 2026, 2:34 PM
tobiu closed this issue on Mar 10, 2026, 2:34 PM