---
name: fantasia-he-tree
description: >-
Hierarchical tree UI with @he-tree/vue only — full project replacement for
Quasar QTree (forbidden). Virtualization, drag-and-drop, Quasar slot styling.
Use when adding or changing any nested tree in the renderer.
---
# Fantasia Archive — hierarchical trees (`@he-tree/vue`)
## Policy
Enforced detail: [fa-he-tree.mdc](../../rules/fa-he-tree.mdc).
- **`@he-tree/vue`** only tree UI (**`package.json`** dependency).
- **Quasar `QTree` / `q-tree` forbidden** — production, dialogs, layouts, Storybook, experiments.
- Upstream: [hetree.phphe.com](https://hetree.phphe.com/v2/guide/) (Vue 3 / v2).
## Why he-tree (not QTree)
**`QTree`** excluded. **`@he-tree/vue`**: virtual list for scale, optional DnD, slots for Quasar-styled rows.
## Installation (already in repo)
```bash
yarn add @he-tree/vue
```
## Basic usage (Vue 3 + script setup)
```vue
{{ node.label }}
```
- **`BaseTree`** — same API without drag when reorder not required.
- Import **`@he-tree/vue/style/default.css`** in owning SFC or wrapper.
## Virtualization checklist
1. Prop **`virtualization`** on **`BaseTree`** / **`Draggable`**.
2. Fixed **`height`** or **`max-height`** on tree or bounded scroll parent.
3. Lazy-load children from main/SQLite on first expand for huge projects.
4. Avoid expand-all on huge trees in one tick.
Related props: **`virtualization`**, **`virtualizationPrerenderCount`**.
## Drag-and-drop
- **`Draggable`** when users reorder nodes.
- **No** **`vue-draggable-plus`** on trees — he-tree owns hierarchical reorder ([fantasia-drag-drop](../fantasia-drag-drop/SKILL.md)).
- Tune **`dragOverThrottleInterval`** on large trees.
- Persist via Pinia + IPC after drop; validate in main with Zod where structured.
### DnD + scroll preservation (layout tree playbook)
Full postmortem: this skill section below (**DnD + scroll preservation**). Reference: **`DialogProjectSettingsWorldTemplateLayoutTree.vue`**.
**Symptom:** drop moves data OK; **`scrollTop`** jumps (often top). Or add row does not scroll into view.
| Cause | Fix |
| --- | --- |
| **`:key` on `Draggable`** changes on reorder/remount | No `:key` for sort; **`resyncTreeDataFromProps`** updates **`treeData`** |
| Topology key uses draft **array order** or **sort fields** | Canonical key: sorted ids + **`groupId`** only — **`mapDialogProjectSettingsWorldTemplateLayoutToTreeStructureKey`** |
| Resync rebuilds **`treeData`** when topology unchanged | Match keys → **`patchWorldTemplateLayoutDisplayLabelsInHeTreeNodes`** only |
| **`overflow: auto`** on wrapper, not he-tree root | Scroll on **`.dialogProjectSettingsWorldTemplateLayoutTree`**; host sizing only — **`resolveDialogProjectSettingsWorldTemplateLayoutTreeScrollContainer`** |
| Post-drop **`scrollTop` restore** | **Do not** — fights virtualization; fix remount/rebuild instead |
**Workspace hierarchy (`ProjectHierarchyTree`):** expand truth = **`openNodeIds` / he-tree stats** — never derive drag expand snapshots from mounted DOM rows (virt omits off-screen). No **`:key` remount**; soft resync only. Post-drop expand reapply can zero vtlist **`scrollTop`** — preserve/restore around commit finalize (not a substitute for remount). he-tree does **not** forward `@virtual-list/vue` **`buffer`**; hierarchy manager mutates VirtualList default (~10 row heights) before mount.
**Pipeline:** `@before-drag-start` → v-model during drag → `@after-drop` → deferred **`emitLayoutFromTreeDataIfChanged`** → props watch **`resyncTreeDataFromProps`**. Append: separate count watch → **`scheduleScrollContainerToRevealLastItem`**.
**Debug:** compare topology keys layout vs **`mapHeTreeNodesToWorldTemplateLayoutDraft(treeData)`** after drop; check resync rebuild vs patch; find real scroll element in DevTools.
## Data and architecture
| Concern | Location |
| --- | --- |
| Node row UI, locators | Feature **`.vue`** (thin script) |
| DB → nodes, filter, selection | Feature **`scripts/`** or **`src/scripts//`** |
| Shared walk/flatten/id-index | Colocate with feature until ≥2 consumers need same helpers (no **`src/scripts/faHeTree/`** yet) |
| Shared interfaces | **`types/I_*.ts`** (`app/types/...`) |
Two-level: pure transforms in **`functions/`** (`import type` only); managers wire stores + IPC.
## Styling
- Override defaults in feature **`styles/`** ([component-styles-folder.mdc](../../rules/component-styles-folder.mdc)); BEM + semantic **`$`** tokens ([project-scss.mdc](../../rules/project-scss.mdc)).
- **`hasScrollbar`** when gutter stability matters.
- User strings in **`i18n/`**; node labels from data OK dynamic.
## Utilities
```ts
import { walkTreeData } from '@he-tree/vue'
walkTreeData(nodes, (node, index, parent) => {
// visit
}, { childrenKey: 'children' })
```
Use **`walkTreeData`** for search, bulk expand, validation — not ad hoc recursion everywhere.
## Project Settings — world template layout
**`DialogProjectSettingsWorldTemplateLayoutTree.vue`** — **`Draggable`**, max depth 2, DnD rules in **`dialogProjectSettingsWorldTemplateLayoutDnD.ts`**, commit policy + wiring in feature **`scripts/`**. DnD scroll playbook: this skill **DnD + scroll preservation**. Full map: [fa-he-tree.mdc](../../rules/fa-he-tree.mdc) and [fa-drag-drop-lists.mdc](../../rules/fa-drag-drop-lists.mdc).
## Tests
- **Vitest** — mount SFC; stub IPC; assert **`data-test-locator`**
- **Playwright** — locators; rebuild Electron when wiring changes ([fantasia-testing](../fantasia-testing/SKILL.md))
- **Storybook** — modest mocked tree; import default CSS
## Related docs
- [fa-he-tree.mdc](../../rules/fa-he-tree.mdc)
- [AGENTS.md](../../../AGENTS.md) Trees / DnD
- [fantasia-quasar-vue](../fantasia-quasar-vue/SKILL.md)