hue tree / DAG view — Feature Requirements (interactive component)
Status: partial — the tree component + the TUI file explorer shipped (d47a0d01); DAG views planned · Date: 2026-07-30 · Scope: a reusable interactive tree and DAG view component — a sparkles:ui widget rendered on GUI / TUI / HTML. TUI design reference: folke/snacks.nvim's explorer. Use cases: file explorer, tree-sitter inspector, file outline, git graph, dependency graph.
NOTE
Forward-looking — every row is not started. Substrate: sparkles:core-cli already ships a static tree producer (ui.tree) and the tree-view case study grounds the interactive design; this component adds interaction (expand/collapse state, cursor, mouse, filtering) and DAG support. Status legend and IDs: see the overview.
IMPORTANT
The generic tree is a toolkit component, specified as WGT12/VMD1–VMD6; this page specifies hue's use of it. Two consequences worth stating up front:
TVU1becomes the directory target. A directory argument opens the file explorer rather than the bespoke index view, and the static gallery becomes the explorer's HTML flavor.- The case study's verdict is take snacks' features, ratatui's architecture: snacks' own tree is an incidental data structure (a mutable singleton with parent back-references, and view state and decoration stored on data nodes), so the feature set is the reference but the three-layer split — data / interaction state / view, with a pure
flattenbetween them — is the shape. - Expand/collapse state is the toolkit's shared disclosure machine (
STM5), the same one folding uses.
Design & rationale
The tree/DAG view is a level-3 widget in the UI component library (ui-architecture.md WGT): one view(state) → Widget definition, rendered on all three targets. Its structure maps onto the library's levels:
- State machine (level 1,
STM) — expand/collapse of nodes (the same collapse model as content foldingFLD2), cursor/selection, and viewport. Presentation-free. - Layout (level 2,
LAY) — indented rows for trees; rail/lane or layered placement for DAGs. - Rendering — per-node icon + label + decorations, indent guides, edges; painted per backend (canvas / cells / HTML).
It is the shared component behind several hue features that today would each hand-roll a tree: the tree-sitter inspector overlay (TSI), a file outline, and more (see TVU). The core-cli ui.tree static renderer is the precedent to generalize.
Tree view component (TRV)
Modeled on snacks.nvim's explorer for the TUI idiom.
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| TRV1 | A tree view must render a hierarchical node model as indented rows: each node an icon + label + optional trailing decorations/badges, with indent guides and an expand/collapse marker on parent nodes. | full (d47a0d01) — tree_widget.treeView: guides, marker, DbI icon/label; badges open | proposed sparkles:ui tree widget; core-cli.ui.tree |
| TRV2 | Expand/collapse must be a presentation-free state machine (shared with folding FLD2 / STM); collapsing hides a subtree, expanding reveals it; expand-all / collapse-all / expand-to-level. | full (d47a0d01) — the shared DisclosureState (STM5), also driving folding | STM; FLD2 |
| TRV3 | Navigation — a cursor moves by visible row (↑/↓, j/k); ←/h collapses (or moves to parent), →/l expands (or enters first child); Home/End, page scroll; mouse click selects, click on the marker toggles (GUI/TUI SGR mouse); the pane shows a scrollbar when it overflows, and the wheel scrolls the pane under the cursor (tree or document), not the focused one. | partial (d47a0d01) — ↑↓/jk/Home/End/PgUp/PgDn, ←/→ collapse-or-parent / expand-or-pick, click selects (second click activates); marker-click toggle open | gui.d/previewer.d input; TIN |
| TRV4 | Lazy children — a node's children may be produced on demand (for large or filesystem-backed trees), so expansion, not construction, drives cost. | full (d47a0d01) — the explorer loads children one level past open (honest markers), recursing only the visible+open chain | proposed node-provider callback |
| TRV5 | Filtering / live search — an incremental filter must narrow visible nodes (matching nodes + their ancestors kept), snacks-explorer style. | full (d47a0d01) — the explorer's / filter: rebuild per keystroke, matches + ancestors (broot mode) | reuse FND input model |
| TRV6 | Per-node decorations must be data-driven — icon (Nerd-Font, with the FNT8 tofu caveat), label style, and trailing badges (e.g. git status, counts) — supplied by the use-case adapter (TVU), not hardcoded. | partial (d47a0d01) — DbI icon/label/slot capabilities; trailing badges (git status) open | adapter-supplied node view |
| TRV7 | Selection must yield a stable node identity / payload to the caller (e.g. a file path, a CST node, a commit) so actions (open, reveal, jump) act on it. | full (d47a0d01) — the explorer returns the picked file path; the caller opens it | node payload contract |
DAG support (DAG)
Trees are the common case; several use cases are directed acyclic graphs (shared children, multiple parents) that a strict tree can't express.
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| DAG1 | The model must accept a DAG (a node reachable by multiple parents): the same node shown once with in/out edges, not duplicated per path; a cycle guard must degrade a mis-supplied cyclic graph safely. | not started | proposed graph model |
| DAG2 | A rail/lane renderer must draw commit-graph-style edges — the │ ├ ╯ ╰ ┬ lane glyphs of git log --graph — for linear-ish DAGs (git graph); reusing native box-drawing (no procedural BOX in the TUI). | not started | git-graph lane layout |
| DAG3 | A layered (Sugiyama-style) node-link renderer must place a general DAG in ranks with routed edges — for dependency graphs; may be GUI-first (the canvas suits free node-link), with a rail/indented fallback on the TUI. | not started | layered graph layout (GUI canvas) |
| DAG4 | The renderer choice (indented-tree-with-backedges · rail/lane · layered node-link) must be per use case; the model is one, the presentation is selected by the adapter (TVU). | not started | TVU adapter selects renderer |
Use cases (TVU)
Each use case is a thin adapter supplying the node model + per-node decorations + renderer choice; the component is shared.
| ID | Use case | Model → adapter | Status | Traces to |
|---|---|---|---|---|
| TVU1 | File explorer | filesystem tree (lazy dirs TRV4), file-type + git-status icons/badges (TRV6), open/reveal actions (TRV7) — the snacks-explorer use case; entered by a directory CLI target (SRC4). | partial — the TUI + GUI workspace panes ship with badges, filters and re-rooting; the HTML flavor (XPL1) is open | proposed FS adapter; sparkles:build-primitives walker; SRC4 |
| TVU2 | Tree-sitter inspector | the CST as a tree (named/anonymous nodes, field names, S-expression); renders the overlays.md TSI panel. | not started | sparkles:tree-sitter CST; TSI |
| TVU3 | File outline | document symbols — code structure (functions/classes) from the CST, or headings from MdDoc — a jump-to-symbol outline. | not started | sparkles:syntax CST / md/model.d; FSR3 |
| TVU4 | Git graph | the commit DAG, rail/lane rendered (DAG2); refs/branches as node badges. First concrete consumers: pick two commits → diff them (DVS3) and the stacked-PR tree (DPR5, the git-spice/av fliptree). | not started | git adapter → DAG2 |
| TVU5 | Dependency graph (build) | a build-system / module DAG (targets → deps), layered or rail rendered (DAG3/DAG2). | not started | build-graph adapter → DAG3 |
| TVU6 | Changed-files tree | the diff/PR session (DVS4) as a tree: per-file status glyphs (the XPF1 git-status machinery), ± line stats, viewed-mark checkboxes (DCM5), conflict badges (CFV3); selecting a file jumps the diff pane (DVG1). | full (2ac6dd0b) | diff-session adapter → TRV; DVS4 |
Explorer workspace integration (XPL)
The explorer is not a separate mode but hue's workspace shell: a neovim-style split with the tree in a left pane and the open document in the right pane, on every backend. The current full-screen tree → viewer → tree loop is the interim shape it replaces.
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| XPL1 | The explorer must be available on all three targets — TUI, GUI, and HTML (the gallery's index becomes the tree's static flavor, per TRB3's pure-CSS <details> doctrine) — one tree definition, per-backend canvases. | not started | TRB1–TRB3; gallery.md GAL5 |
| XPL2 | Split-pane layout (neovim snacks.explorer style): the tree in a left sidebar, the currently open document in the right pane — one window/screen, two viewports; the sidebar is toggleable and the split ratio is configurable. | partial (3116fb2c+) — the TUI and GUI workspaces ship (toggleable sidebar, one loop each, focus routing; --tree-width seeds the split and dragging the divider resizes it live on both backends (STM8); the GUI pane gained the live filter); the HTML flavor is open | LAY7 viewports; ui/layout.md |
| XPL3 | The currently open document is highlighted in the tree (a distinct row style from the cursor), and stays highlighted as the tree is scrolled, filtered, or re-expanded; both indicators (open document, cursor row) take their colors from the current theme, not from palette constants. | full (0648bdc3) — TUI: the open document's node carries a distinct slot through rebuilds | TRV7 node identity; Slot.selection |
| XPL4 | Prev/next document navigation ([/], set navigation) must update the explorer too: the new document's node is selected and revealed (ancestor dirs auto-expanded, scrolled into view) — the tree and the viewer never disagree on "current". | full (3116fb2c) — TUI: [/] walk the tree's files; GUI: every openPath (tree, set navigation, startup) reveals | navigation.md GNV; TRV3 |
| XPL5 | The explorer must follow the current theme: cycling the theme re-skins the tree (page colors + the palette its slots resolve against) in the same frame, on every backend — not just the document's syntax colors. | full (36cf5cea) — GUI applyTheme re-skins the tree; the TUI workspace re-resolves it after every theme cycle | applyTheme/workspace theme sync; ui/theme.md THM |
| XPL6 | Tree rows must show file-type icons (vscode-icons / snacks-explorer style): per-extension Nerd glyphs with their brand colors, a distinct icon for an open vs closed directory, and a generic fallback — data supplied by the FS adapter through the tree view's DbI icon capability, never hardcoded in the component (TRV6). The folder icon is the disclosure indicator: the explorer suppresses the separate ▸/▾ marker (it would duplicate the icon), and one space separates the guide connector from every icon. | full (36cf5cea) — per-extension Nerd glyphs + brand colors via the treeView iconFg capability; open/closed folder pair from the disclosure state | Nerd devicons; FsEntry.icon; TRV6 |
Explorer feature scope (XPF)
Scoped against a survey of snacks.nvim's explorer (2026-07-30). In: the pure filesystem/git features below. Out, by decision: file operations (hue stays a viewer — add/rename/delete/move/copy/paste belong to the shell/editor), filesystem watching (manual refresh is the contract instead), LSP-coupled rename/buffer follow-through, and a separate deep-search mode (the existing live filter already searches the whole tree).
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| XPF1 | Git status integration: one async git status --porcelain -z --ignored=matching per repo root with a TTL cache and a generation guard (a stale in-flight result must never clobber a newer one); per-file badges; worst-status-wins ancestor propagation (ignored deliberately not propagated); whole-ignored/untracked-dir status inherited by contents without listing them; git-ignored rows dimmed; ]/[ next/prev-change navigation while the tree pane is focused (the viewer keeps the brackets for document navigation; a two-key ]g sequence cannot coexist with the workspace's bracket bindings). | full (afd3a1d1) | git_status.d; FsEntry.badge/gitSt; jumpChange |
| XPF2 | Filters & toggles: a hidden-dotfile toggle and a git-ignored toggle (runtime keys, state shown in the chrome), plus glob include/exclude with snacks' precedence — include overrides hidden, ignored, and exclude. | full (7200a489+) — H/I toggles + chrome state; --include/--exclude globs with snacks precedence (one visible() predicate) | visible/globAny; --include/--exclude |
| XPF3 | Re-rooting + close-all: re-root to the selected item's directory, re-root to the parent, re-root outward when revealing a file outside the root (XPL4), and collapse-all. | full (361f5eb8) — Shift-R/u re-root (reveal re-roots outward), c collapses | rerootSel/rerootParent/closeAll; reveal |
| XPF4 | Manual refresh: a refresh key re-reads the filesystem (invalidating loaded children and the git cache) while preserving the open set — the open/expanded split's payoff. This is the deliberate alternative to filesystem watching. | full (afd3a1d1) — r re-lists + forces the git snapshot, preserving the open set | refreshNow; the open/expanded split |
| XPF5 | Diagnostics badge seam (future): a provider contract — any source of per-file severities (a compiler/linter channel) feeds worst-severity-wins propagation onto files and ancestors, rendered like the git badges. The propagation is trivial; only the source is missing, so this row waits on one. | researched | snacks diagnostics.lua (40-line propagation) |
Per-backend rendering (TRB)
| ID | Requirement | Status | Traces to |
|---|---|---|---|
| TRB1 | GUI — canvas rows + indent guides + edges via sparkles:raylib-text primitives; mouse hit-test; the layered DAG renderer (DAG3) is GUI-first. | not started | gui.md; TGT1 |
| TRB2 | TUI — cell rows, box-drawing indent guides + git-rail glyphs (native), SGR-mouse + keys; snacks-explorer parity. | not started | tui.md; core-cli.ui.tree |
| TRB3 | HTML — a nested <ul>/<details> tree with pure-CSS expand/collapse (no JS — the folding/notifier doctrine); DAG as a rail SVG/CSS or indented-with-backedges. | not started | app.d HTML branch; FLD10 |
Milestones
| Milestone | Scope | Status | Requirements |
|---|---|---|---|
| V0 | Interactive tree (expand/collapse state, cursor, mouse) — GUI + TUI | not started | TRV1–TRV3, TRB1/2 |
| V1 | Lazy children + filtering + decorations; the file-explorer adapter | not started | TRV4–TRV7, TVU1 |
| V2 | Tree-sitter inspector + file outline adapters | not started | TVU2, TVU3 |
| V3 | DAG model + git-rail renderer; the git-graph adapter | not started | DAG1, DAG2, TVU4 |
| V4 | Layered node-link renderer; the dependency-graph adapter (GUI-first) | not started | DAG3, TVU5 |
| V5 | HTML <details>/rail rendering | not started | TRB3 |
Relationship to existing specs
| Piece | Role |
|---|---|
ui-architecture.md WGT/STM/LAY | the widget/state/layout levels this component instantiates |
folding.md FLD2 | shares the expand/collapse state machine |
overlays.md TSI | the tree-sitter inspector — rendered by TVU2 |
sparkles:core-cli ui.tree (static) | the precedent renderer to generalize (TRV1) |
| tree-view case study | interactive-tree design grounding |
| docs/specs/tui | the sparkles:tui cell-grid substrate (an interactive tree is a named consumer there) |
→ UI architecture · Overlays · GUI requirements · TUI requirements · Overview