Johnny Decimal Dashboard

unlisted

by Nelson Love

Live JD system awareness — inbox dashboard, drift detection, quick ID navigation, template-driven note creation.

Updated 21d ago
View on GitHub

Johnny Decimal Dashboard — Obsidian Plugin

Live JD system awareness inside the Obsidian vault. Inbox dashboard, drift detection, vault auditing, frontmatter normalization, and quick ID navigation.

Companion plugin to the jd-tools CLI suite. Reads the same jd-index.yaml and jd.yaml that jd-cli uses, but has no runtime dependency on it — either tool can be installed and used independently. The two coordinate purely through the shared YAML files.

Install (BRAT)

  1. Install the BRAT community plugin.
  2. BRAT → Add beta pluginnelsonlove/obsidian-jd-dashboard.
  3. Enable Johnny Decimal Dashboard in Community plugins.

Upgrading from ≤0.2.0: the Render filesystem contents command, its AI-provider settings, and the stale-surveyed audit check were removed in 0.3.0 — the ## Contents (Filesystem) survey domain now belongs to the jd-survey plugin. Retired keys in data.json are cleaned up automatically on load. API keys the old feature stored in the OS keychain (service entries for anthropic-api-key / openai-api-key) are no longer read; remove them with Keychain Access if you want them gone.

Features

Inbox Dashboard

Sidebar panel showing all .01 Unsorted/Inbox directories with item counts, grouped by area, busiest-first. Click a row to reveal the folder in the file explorer. Live-updates on vault changes.

Drift Panel

Sidebar panel showing notes with frontmatter/location issues, grouped by type:

  • Missing frontmatter — JD ID in filename but no jd-id field. One-click fix button.
  • ID mismatch — filename and frontmatter disagree on the ID.
  • Wrong folder — note is in the wrong category directory.
  • Missing stubs — JDex entries without a corresponding note. One-click create button.

Status bar shows "JD: N drifted" — click to open the panel.

Notes can opt out of any drift check via the jd-ignore frontmatter field; ignores cascade from folder cover notes.

Vault Audit

Comprehensive health check with 9 validation rules across 3 severity levels:

SeverityChecks
ErrorMissing required fields, invalid date formats, invalid categories, duplicate IDs
WarningOrphaned files, title mismatches, missing stubs
InfoBroken wikilinks, empty notes

Generates a markdown report at 00.00+REPORT JD vault audit.md. Optional auto-run on startup. Honors jd-ignore.

Frontmatter Normalizer

Auto-corrects frontmatter on save. Each behavior is independently toggleable in settings; the keys it operates on are configurable.

BehaviorDefaultSetting
Master switchonEnable normalizer
Quote ID values (so YAML doesn't read 06.12 as a float)onQuote ID values
Infer type when missing (from ID pattern)onInfer type when missing
Sort frontmatter keys into canonical orderonSort frontmatter keys
Strip # XX.YY Title heading prefix to # TitleonStrip ID from H1 heading

The type can be expressed as a frontmatter key (default) or as a tag — see Type representation.

Quick ID Navigation

Command palette: JD: Go to ID — fuzzy search by JD ID number or title.

Drift Report

Generates 00.00+REPORT JD drift report.md with drift details, inbox summary, and missing stubs.

+README Migration

One-shot migration command for vaults coming from the legacy +README cover-note scheme — see Cover notes.

Render category JDex contents

Walks every XX.00 JDex for category XX.md file and regenerates a bullet list of [[ID Title]] wikilinks for every entry in that category — both JDex YAML entries and 5-digit filesystem-only items (e.g. 92001 Substrate). The list is wrapped in HTML sentinel comments so prose above and below is preserved across runs:

<!-- jd:render-start -->
- [[01.01 Inbox for category 01]]
- [[01.06 Knowledge base for category 01]]
...
<!-- jd:render-end -->

Also flags drifted wikilinks in the existing content — references with bad IDs (e.g. [[76.09 Archive for category 77]] when the entry is actually 77.09) are logged to the console.

Promote note to folder

Converts the active ID note (06.13 Bar.md) into a same-named folder with the note moved inside as the cover note (06.13 Bar/06.13 Bar.md). Useful when a leaf note grows enough to need siblings.

Commands

CommandDescription
JD: Open inbox dashboardOpen the inbox sidebar panel
JD: Open drift panelOpen the drift detection sidebar
JD: Go to IDFuzzy search by ID or title
JD: Run vault auditGenerate comprehensive health report
JD: Generate drift reportWrite drift/inbox markdown report
JD: Check for driftQuick drift count (status bar + console)
JD: Migrate +README files to folder-named cover notesOne-time migration from the legacy scheme
JD: Render category JDex contentsRegenerate XX.00 JDex files from JDex YAML + filesystem
JD: Promote note to folderConvert active ID note to same-named folder with note moved inside

Settings

Paths

SettingDefaultDescription
JD root~/DocumentsFilesystem root of the JD tree
JDex path~/.local/share/jd/jd-index.yamlPath to the JDex index file

Dashboard / Audit

SettingDefaultDescription
Show empty inboxesoffShow inbox folders with 0 items
Audit on startupoffRun vault audit when Obsidian opens

Frontmatter keys

The keys can be renamed; all checks, generators, and the normalizer use the configured names. Changing these does not migrate existing notes.

SettingDefaultNotes
Title keyjd-title
ID keyjd-id
Type keyjd-typeIgnored when "Type as tag" is on
Ignore keyjd-ignoreSee Opting out

Frontmatter normalizer

All five toggles default on, preserving prior behavior.

SettingDefaultDescription
Enable normalizeronMaster switch
Quote ID valuesonWrap unquoted IDs in single quotes
Infer type when missingonAdd type from ID pattern
Sort frontmatter keysonReorder keys into canonical order
Strip ID from H1 headingonRewrite # XX.YY Foo as # Foo

Type representation

SettingDefaultDescription
Type as tagoffExpress type as a tag instead of a frontmatter key
Type tag prefixjd/Prefix prepended to the type value (e.g. jd/inbox)
Type tag overrides(empty)One per line as type: tag. Overrides the prefix-based tag for that type.
Persist generic 'id' typeonOff → don't write a type when the inferred value is the generic id

When Type as tag is on, the normalizer (and report generators) emit:

tags:
    - jd/inbox

instead of jd-type: inbox. Per-type overrides let you replace the prefix entirely; e.g. mapping knowledge-base: kb produces the tag kb instead of jd/knowledge-base.

Cover notes

A folder representing a JD ID (e.g. 06.12 Foo/) gets a cover note inside it whose basename matches the folder name: 06.12 Foo/06.12 Foo.md. The cover note's jd-id is the bare ID — no +README suffix.

---
jd-id: '06.12'
jd-title: Foo
jd-type: id
created: 2026-04-29
---

# Foo

## Contents

This replaces the older convention where the cover note was XX.YY+README.md with an aliases: block. Categories are unchanged: they still use a XX.00 JDex for category XX.md index file rather than a cover note. Use JD: Migrate +README files to folder-named cover notes to convert legacy vaults; the command updates wikilinks via Obsidian's fileManager.renameFile.

Opting out with jd-ignore

Any note can opt out of drift/audit checks via its jd-ignore frontmatter:

jd-ignore: true                       # silence all checks
jd-ignore: [title-mismatch]           # silence specific checks
jd-ignore: title-mismatch, empty-note # comma-separated form

Cascade: the ignore list of a folder's cover note also applies to every note in that folder (and recursively). Set jd-ignore: true on 92001 Substrate/92001 Substrate.md to silence the whole subtree.

Recognized check names match what the audit/drift reports emit: missing-frontmatter, id-mismatch, wrong-folder, title-mismatch, required-fields, date-format, valid-category, duplicate-id, orphaned-file, broken-wikilink, empty-note, missing-stub.

Frontmatter schema

---
jd-title: Restraining order
jd-id: '26.11'
jd-type: id
jd-location: filesystem
created: 2026-03-31
modified: 2026-04-23
surveyed: 2026-03-31
---

jd-type values

TypeSourceDescription
idleaf IDsRegular JD ID note
index.00JDex/meta note
inbox.01Inbox/unsorted
tasks.02Task & project management
templates.03Templates
links.04Link collections
policies.05Conventions & policies
knowledge-base.06Knowledge base
someday.08Someday/maybe
archive.09Archive
report+REPORTGenerated reports
audit+AUDITAudit documents
metaother + sub-IDs+CLAUDE, +INDEX, etc.

Development

npm install
npm run dev     # watch mode — rebuilds on change
npm run build   # production build

Releases are cut by pushing a bare X.Y.Z tag matching manifest.json; CI builds the plugin and publishes a GitHub Release with the built files attached, which is what BRAT installs.

The plugin is symlinked into the vault at .obsidian/plugins/jd-dashboard/ for development. After building, reload Obsidian (Cmd+R) to pick up changes.

Architecture

src/
├── main.ts                  # Plugin entry point — lifecycle, commands, events
├── settings.ts              # Settings tab and defaults
├── keys.ts                  # Key-name and tag/ignore helpers driven by settings
├── ignores.ts               # Cascading jd-ignore resolution
├── jdex.ts                  # JDex YAML parser and lookup helpers
├── scanner.ts               # Inbox counter, drift detector, stub finder
├── normalizer.ts            # Frontmatter auto-correction on save (5 toggles)
├── validator.ts             # Validation checks, report engine
├── commands/
│   ├── go-to-id.ts          # SuggestModal for fuzzy ID search
│   ├── drift-report.ts      # Markdown drift report generator
│   ├── audit-report.ts      # Markdown audit report generator
│   ├── migrate-readme.ts    # Legacy +README → folder-named migration
│   ├── render-jdex.ts       # Regenerate XX.00 JDex contents
│   └── promote-to-folder.ts # Convert leaf note to ID directory + cover note
└── views/
    ├── inbox-dashboard.ts   # Sidebar: inbox counts by area
    └── drift-panel.ts       # Sidebar: drifted notes with fix buttons

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.