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.
@he-tree/vueonly tree UI (package.jsondependency).- Quasar
QTree/q-treeforbidden — production, dialogs, layouts, Storybook, experiments. - Upstream: hetree.phphe.com (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)
yarn add @he-tree/vue
Basic usage (Vue 3 + script setup)
<template>
<Draggable
v-model="treeData"
virtualization
class="myFeatureTree hasScrollbar"
:style="{ height: treeHeightPx + 'px' }"
data-test-locator="myFeature-tree"
>
<template #default="{ node, stat }">
<!-- Quasar + i18n inside the slot -->
<span
class="myFeatureTree__label"
:data-test-locator="'myFeature-tree-node-' + node.id"
>
{{ node.label }}
</span>
</template>
</Draggable>
</template>
<script setup lang="ts">
import { Draggable } from '@he-tree/vue'
import '@he-tree/vue/style/default.css'
const treeData = defineModel<Array<{ id: string, label: string, children?: unknown[] }>>('treeData', {
required: true
})
const treeHeightPx = 400
</script>
BaseTree— same API without drag when reorder not required.- Import
@he-tree/vue/style/default.cssin owning SFC or wrapper.
Virtualization checklist
- Prop
virtualizationonBaseTree/Draggable. - Fixed
heightormax-heighton tree or bounded scroll parent. - Lazy-load children from main/SQLite on first expand for huge projects.
- Avoid expand-all on huge trees in one tick.
Related props: virtualization, virtualizationPrerenderCount.
Drag-and-drop
Draggablewhen users reorder nodes.- No
vue-draggable-pluson trees — he-tree owns hierarchical reorder (fantasia-drag-drop). - Tune
dragOverThrottleIntervalon large trees. - Persist via Pinia + IPC after drop; validate in main with Zod where structured.
DnD + scroll preservation (layout tree playbook)
Full postmortem: .cursor/plans/he-tree-dnd-scroll-playbook_v2.4.14_2026-06-24-00-00-17.plan.md. 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 |
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/<domain>/ |
| Shared walk/flatten/id-index | src/scripts/faHeTree/ when reused |
| 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); BEM + semantic$tokens (project-scss.mdc). hasScrollbarwhen gutter stability matters.- User strings in
i18n/; node labels from data OK dynamic.
Utilities
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: he-tree-dnd-scroll-playbook plan. Full map: fa-he-tree.mdc and fa-drag-drop-lists.mdc.
Tests
- Vitest — mount SFC; stub IPC; assert
data-test-locator - Playwright — locators; rebuild Electron when wiring changes (fantasia-testing)
- Storybook — modest mocked tree; import default CSS
Related docs
- fa-he-tree.mdc
- AGENTS.md Hierarchical trees (he-tree)
- fantasia-quasar-vue