Problem
Group organizations (adventuring parties, guilds, chains of command) are currently modeled outside the plugin entirely, via a dataviewjs block that hand-builds a Mermaid flowchart string from frontmatter arrays (leader, officers, members, initiates). It's a genuinely careful piece of script — stripWikiLinks handles Dataview's own Link objects (.path/.display), falls back to regexing raw [[Path|Display]] strings, and strips folder prefixes so nested notes still show just their filename — but it's still a workaround, not a native feature.
This works, but has real costs:
- The rank structure is hardcoded in script logic.
leader → officers → members → initiates is baked into the dataviewjs code itself, right down to which Mermaid node ID feeds into which. A guild with Master/Journeyman/Apprentice, or a military note with Commander/Captains/Sergeants/Soldiers, needs its own copy-pasted variant of the script — there's no way to reuse one hierarchy shape across many notes, or define a second shape for a different kind of group.
- Frontmatter has to be shaped around the script, not the data.
leader is read as dv.current().leader?.[0] — a single person still has to be entered as a one-item list just so the array indexing works, rather than the plain scalar-or-list flexibility every other property in the vault gets.
- The result is a styled label, not a resolved link. Each node gets a
:::internal-link class so it visually resembles one of Obsidian's own wikilinks (blue, underlined) — an explicit workaround for the fact that a Mermaid node is otherwise just inert text. It doesn't pick up the note's portrait, doesn't participate in the vault's link graph, and click-to-navigate isn't guaranteed the way a real link is.
- No visual integration with the rest of the vault's graphs. Mermaid's flowchart styling doesn't match Relations' theme-aware node/edge rendering used everywhere else (portraits, ring colors, legend, dark/light theme awareness) — the
internal-link class only gets it partway there.
- No interactivity beyond that. No pan/zoom, no locking a layout in place — things the plugin's existing Cytoscape-based graph views already provide for relationship/family graphs.
- Fragile to maintain. Every group note carries a duplicated dataviewjs block; a bug fix or styling tweak has to be propagated by hand across every note that uses it.
Proposed Solution
Add a native Organization Hierarchies feature to Relations:
- Users define one or more named hierarchies in settings (e.g. "Party Structure", "Guild Ranks"), each with an arbitrary number of levels — a number (gaps allowed), a name, a color, and a line style per level — instead of one hardcoded shape.
- A level's name converts to a frontmatter field (
Officers → officers); a Group note lists its members under those fields, same convention as every other relationship property in the plugin.
- A relations code block renders the structure with
org-tree: <hierarchy name> (top-down dagre) or org-graph: <hierarchy name> (force-directed), mirroring the existing family-tree/family-graph split.
- Rendering reuses the plugin's existing Cytoscape pipeline, so portraits, theme-aware colors, click-to-open, pan/zoom, and layout-locking all work identically to every other graph mode — no separate rendering path to maintain, no Mermaid/dataviewjs dependency.
- A level with one member shows that member's own portrait directly (ringed in the level's color); a level with several members gets a colored hub node labeled with the level name, fanning out to its members — replacing the old script's synthetic
L, OG, MG, IG Mermaid nodes with real, theme-integrated graph nodes.
Implementation Plan
- Data model — add
OrganizationHierarchy/OrganizationLevel types (name, levels: number + name + color + line style) and an organizationHierarchies array on plugin settings, with a shipped default hierarchy.
- Settings UI — an "Organization hierarchies" section listing configured hierarchies (Edit/Delete), a two-step "Add hierarchy" modal (name, then a level builder: number/name/color/line-style rows with inline duplicate-number validation and gap warnings).
- Frontmatter → graph — a level-name → field-name converter, a hierarchy/name lookup, and a graph builder that reads each level's frontmatter field, resolves wikilinks, collapses single-member levels to that member's own node, and builds colored hub nodes with fan-out edges for multi-member levels, chained top-down.
- Rendering — extend the shared node/edge model with a
fillColor field for hub nodes; style hub labels as always-visible, centered, outlined text (no pill) so the fill color stays legible.
- Code-block parsing —
org-tree:/org-graph: parameters, resolved the same way as family-tree:/family-graph:, selecting the hierarchy name and layout engine.
- Tests — unit tests for the field-name conversion, level validation, and graph-building logic (single/multi-member collapsing, empty-level skipping, unresolved-link handling), plus code-block-option parsing tests.
- Docs — README section covering hierarchy definition, the frontmatter convention, code-block usage, and rendering behavior.
Expected Files to Modify
| File |
Planned Changes |
Type |
src/types.ts |
+58 |
Interfaces |
src/organization-hierarchies.ts |
+282 (new file) |
Core logic |
src/settings.ts |
+351 |
Settings UI |
src/codeblock.ts |
+123 |
Parsing / rendering integration |
src/render.ts |
+31 |
Rendering (hub node styling) |
src/main.ts |
+12 |
Settings migration, command text |
src/graph.ts |
+1 |
Export buildNode for reuse |
styles.css |
+72 |
Settings UI styling |
tests/organization-hierarchies.test.ts |
+306 (new file) |
Tests |
tests/org-mode.test.ts |
+44 (new file) |
Tests |
tests/ring-color.test.ts |
+1 |
Test fixture fix |
README.md |
+64 |
Docs |
Use Case
A guild note currently renders its ranks via:
const stripWikiLinks = (text) => {
if (typeof text === 'object' && text !== null) {
if (text.path) return text.path.split('/').pop().replace(/\.md$/, '');
if (text.display) return text.display;
return String(text);
}
const match = String(text).match(/\[\[(?:.*?\|)?([^\]]+)\]\]/);
return match ? match[1].split('/').pop() : text;
};
const leader = dv.current().leader?.[0] ? stripWikiLinks(dv.current().leader[0]) : null;
const officers = (dv.current().officers ?? []).map(stripWikiLinks);
// ...builds a Mermaid flowchart string by hand, one branch per level,
// each node classed :::internal-link to *look* like a wikilink...
With native hierarchy support, the same note instead has plain frontmatter — leader as a normal scalar, no forced one-item list —
leader: "[[Commander Lysa]]"
officers: ["[[Thorne]]", "[[Kess]]"]
members: ["[[Gareth]]", "[[Mira]]", "[[Bran]]"]
initiates: ["[[Squire Elric]]"]
— and a one-line code block:
org-tree: Party Structure
The rendered graph shows real, clickable, portrait-bearing nodes instead of plain-text Mermaid boxes, uses colors and line styles configured once in settings (reused across every note that references the hierarchy), and supports the same pan/zoom/lock-in-place interactions as the rest of the plugin — with no per-note script to maintain, and no hardcoded rank structure.
Problem
Group organizations (adventuring parties, guilds, chains of command) are currently modeled outside the plugin entirely, via a
dataviewjsblock that hand-builds a Mermaidflowchartstring from frontmatter arrays (leader,officers,members,initiates). It's a genuinely careful piece of script —stripWikiLinkshandles Dataview's ownLink objects(.path/.display), falls back to regexing raw[[Path|Display]]strings, and strips folder prefixes so nested notes still show just their filename — but it's still a workaround, not a native feature.This works, but has real costs:
leader → officers → members → initiatesis baked into the dataviewjs code itself, right down to which Mermaid node ID feeds into which. A guild withMaster/Journeyman/Apprentice, or a military note withCommander/Captains/Sergeants/Soldiers, needs its own copy-pasted variant of the script — there's no way to reuse one hierarchy shape across many notes, or define a second shape for a different kind of group.leaderis read asdv.current().leader?.[0]— a single person still has to be entered as a one-item list just so the array indexing works, rather than the plain scalar-or-list flexibility every other property in the vault gets.:::internal-linkclass so it visually resembles one of Obsidian's own wikilinks (blue, underlined) — an explicit workaround for the fact that a Mermaid node is otherwise just inert text. It doesn't pick up the note's portrait, doesn't participate in the vault's link graph, and click-to-navigate isn't guaranteed the way a real link is.internal-linkclass only gets it partway there.Proposed Solution
Add a native Organization Hierarchies feature to Relations:
Officers→officers); a Group note lists its members under those fields, same convention as every other relationship property in the plugin.org-tree: <hierarchy name>(top-down dagre) ororg-graph: <hierarchy name>(force-directed), mirroring the existingfamily-tree/family-graphsplit.L,OG,MG,IGMermaid nodes with real, theme-integrated graph nodes.Implementation Plan
OrganizationHierarchy/OrganizationLeveltypes (name, levels: number + name + color + line style) and anorganizationHierarchiesarray on plugin settings, with a shipped default hierarchy.fillColorfield for hub nodes; style hub labels as always-visible, centered, outlined text (no pill) so the fill color stays legible.org-tree:/org-graph:parameters, resolved the same way asfamily-tree:/family-graph:, selecting the hierarchy name and layout engine.Expected Files to Modify
src/types.tssrc/organization-hierarchies.tssrc/settings.tssrc/codeblock.tssrc/render.tssrc/main.tssrc/graph.tsstyles.csstests/organization-hierarchies.test.tstests/org-mode.test.tstests/ring-color.test.tsREADME.mdUse Case
A guild note currently renders its ranks via:
With native hierarchy support, the same note instead has plain frontmatter — leader as a normal scalar, no forced one-item list —
— and a one-line code block:
The rendered graph shows real, clickable, portrait-bearing nodes instead of plain-text Mermaid boxes, uses colors and line styles configured once in settings (reused across every note that references the hierarchy), and supports the same pan/zoom/lock-in-place interactions as the rest of the plugin — with no per-note script to maintain, and no hardcoded rank structure.