ADR-0018: Hierarchical filter tree for multi-level resource classes on /reference/
Status: Accepted Date: 2026-05-20
Context
The /reference/ folder page uses FilterableFolderContent (ADR-0015) with flat resource-class pills (CSG, DP, BR…). With CSG ingestion producing 100+ reference notes, class-level filtering is too coarse. Readers need to filter to a Book, a Chapter, or a specific Section.
CSG has a 4-level hierarchy: Book (folder Book01–16) → Chapter (one MD file per chapter) → Section (H1 heading) → Subsection (H2 heading, numbered 1.1.). Future resources like World Scriptures II may share this shape.
Decision
Two-tier filter UI. Class pills remain at the top level. When a hierarchical class is active (e.g. CSG selected), a collapsible tree panel appears below the pills showing only that class’s hierarchy.
Tree data model:
- Built at Quartz build time from
sources:wikilinks across all reference notes. - Only cited nodes appear — phantom Books/Chapters with zero references are hidden.
- Selecting a node is inclusive downward: Book 1 → all refs citing any
CSG/Book01/…wikilink; Chapter 1 → all refs citingCSG/Book01/csg-01-01-…; §1.1 → only refs with that exact subsection anchor. - Single-select only.
Citation depth: Subsection (H2). CSG citations anchor to the H2 subsection: [[CSG/Book01/csg-01-01-the-original-being-of-god#11-the-incorporeal-god|1.1.-the-incorporeal-god]]. The path encodes Book and Chapter; the anchor encodes Section and Subsection. The extractKey parses both.
CSG resource file frontmatter. Each CSG chapter file gains frontmatter during ingestion:
---
type: resource
class: CSG
book: 1
book-title: "True God"
chapter: 1
chapter-title: "The Original Being of God"
---Frontmatter is a reader aid and Obsidian query target. Hierarchy structure derives from wikilink paths. Node labels (book-title, chapter-title) come from resource file frontmatter via allFiles — no extra I/O since allFiles is already loaded at build time.
Explicit opt-in in quartz.config.ts. Non-hierarchical classes (DP) keep flat pills unchanged. Each hierarchical class is declared explicitly with pathPattern and levelNames:
hierarchical: {
CSG: {
pathPattern: /CSG\/(Book\d+)\/(csg-\d+-\d+-)/,
levelNames: ["Book", "Chapter"],
},
"Believers-Responsibility": {
pathPattern: /Believers-Responsibility\/([\w-]+)(?:#([\w-]+))?/,
levelNames: ["Chapter", "Section"],
},
}levelNames.length implicitly declares tree depth (1–3 levels). Path separators (/ vs #) are auto-inferred from the cited path — no extra config field. Label lookup order per level: ${levelName.toLowerCase()}-title frontmatter → title frontmatter → slug-to-title-case. Section-level nodes are filter-only — URL deep-links are not supported at section level because # conflicts with the browser URL fragment.
When World Scriptures II or any future hierarchical class is added, one entry is added here. This ADR is the checklist item — see also CONTEXT.md class-addition checklist.
URL deep-links. Tree state encodes as a path-prefix param: selecting Book 1 → ?sources=CSG/Book01; Chapter 1 → ?sources=CSG/Book01/csg-01-01. Filter matches any sources: wikilink that starts with the param value.
Alternatives considered
- Unified tree for all classes: rejected — DP and BR have no sub-hierarchy; a tree for them would be a flat list with extra chrome.
- Convention-based auto-detection (nested folder = tree): rejected — fragile; a class with incidental subfolders would grow a spurious tree. Explicit config is self-documenting.
- Static per-Book pages (
/reference/csg/book01/): rejected — multiplies page count, loses the single canonical/reference/URL, doesn’t compose with the existing class-pill filter. - Multi-select tree nodes: rejected — use case doesn’t require it; adds UI complexity for no clear reader benefit.
Consequences
- (+) Readers can drill from “all CSG refs” down to a specific subsection with no page reload
- (+) Deep-linkable: any tree state is shareable via URL
- (+) Generalizes cleanly to World Scriptures II and other multi-level resources via one config entry
- (+) Non-hierarchical classes (DP, BR) are unaffected
- (−)
FilterableFolderContentrequires significant new logic: tree-building pass, collapsible UI, prefix-match filter semantics - (−) Every new hierarchical class requires: ADR + CONTEXT.md citation grammar entry + frontmatter schema +
quartz.config.tsentry