--- 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 ``` - **`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)