Skip to content

Feature: Organization Hierarchies #30

Description

@lilmissy4205

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 (Officersofficers); 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

  1. 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.
  2. 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).
  3. 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.
  4. 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.
  5. Code-block parsing — org-tree:/org-graph: parameters, resolved the same way as family-tree:/family-graph:, selecting the hierarchy name and layout engine.
  6. 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.
  7. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions