Skip to content

script-utils/demo-vault-coverage

Static coverage/freshness checks that keep a plugin’s in-repo demo-vault/ in sync with its public surface.

A plugin can ship a curated demo vault at demo-vault/ in its repo root. These helpers reflect the real public API/config/docs straight from source and assert — WITHOUT launching Obsidian — that every feature is demonstrated in the notes and that the notes reference no API member that no longer exists (rename drift). The runtime behavior of the plugin is covered by its other integration tests, not by the demo vault; the vault is a learning resource, and these checks only guard that it stays truthful.

They also guard the vault’s own .obsidian/app.json: the settings listed in DEMO_VAULT_APP_JSON_SETTINGS belong to obsidian-dev-utils, which writes them into the archived vault at release time, so a vault that commits any of them is keeping a second copy nothing reconciles.

They also enforce the demo-vault AUTHORING convention — every note opens with an # H1 and a plain prose paragraph, every note is reachable from the start note, and no note uses a [[wikilink]] or a [Docs](…) line. These checks are unconditional: the demo vault is the published documentation, read in Obsidian AND on GitHub (where a wikilink does not render), so a note that breaks the convention is broken for real readers. authoring only tunes them (which note is the start, which notes sit outside the learning path); it cannot switch them off.

Only rootFolder is required, so a plugin with no settings and no public API to reflect registers the suite with that alone and still gets every authoring check — the checks have nothing to do with what the plugin exposes. Each reflection spec (configInterfaces, interfaces, nonTrivialGuard) adds the checks it describes when supplied. The vault is guarded against being empty either way: with no notes to read, every authoring check would pass by having nothing to look at.

The one exception is a note whose SUBJECT is the wikilink — an embed spelling the reader is being taught to type, a frontmatter property value that exists to contrast with a Markdown link, a fixture that must keep the wikilink a command is supposed to leave alone, a link the reader clicks while it still resolves to nothing. Such a note says so itself, stating why, in one of two forms modelled on ESLint’s.

Per line or per region, written as an HTML comment so it is invisible in Obsidian AND on GitHub — the spelling these vaults already use for markdownlint-disable-next-line:

<!-- obsidian-dev-utils-disable-next-line demo-vault-validation/no-wikilinks -- Clicking a link to a note that does not exist yet IS the feature. -->
2. Click this link: [[Projects/Fresh idea]].
<!-- obsidian-dev-utils-disable demo-vault-validation/no-wikilinks -- The wikilink embed is the syntax this note teaches. -->
![[basic.html|400]]
<!-- obsidian-dev-utils-enable demo-vault-validation/no-wikilinks -->

Per note, in the note’s own frontmatter — the only form that can reach a wikilink INSIDE frontmatter, where a comment cannot go:

---
obsidian-dev-utils:
demo-vault-validation:
allow-wikilinks: The `wikilink` property value is the contrast this note is built around.
---

Either way the declaration travels with the note rather than sitting in a list somewhere else, it carries its justification, and it exempts only the WIKILINK check — the H1, prose, reachability and [Docs] checks still apply. A declaration with no reason, one covering no wikilink, a region never enabled again, or a misspelled directive all fail: an exemption nobody can justify, that nothing needs any more, or that silently does nothing is worse than no exemption at all.

Two layers are exposed: - DemoVaultCoverageChecker — a framework-agnostic core that reads the corpus, parses interface / class / enum members and exported functions, and returns diagnostic arrays (what is undemonstrated / stale / unlinked / off-convention). - registerDemoVaultCoverageSuite — a thin wrapper that registers a vitest suite over the core, so a plugin’s demo-vault.no-app.integration.test.ts is a single declarative call.

ClassDescription
DemoVaultCoverageCheckerReflects a plugin’s public surface from source and checks its in-repo demo-vault/ stays in sync with it. Every method is a pure query over files under the repo root (the demo corpus is read once and cached), so the checker is trivially unit-testable against a fixture repo and carries no test-framework dependency.
InterfaceDescription
DemoVaultAuthoringSpecTunes the always-on authoring checks. It cannot disable them — see the file overview.
DemoVaultConfigInterfaceCoverageSpecReflects a config interface whose options are demonstrated by their bare name.
DemoVaultCoverageCheckerConstructorParamsThe parameters for the DemoVaultCoverageChecker constructor.
DemoVaultCoverageCheckerFindStaleReferencesParamsThe parameters for DemoVaultCoverageChecker.findStaleReferences.
DemoVaultCoverageCheckerFindUndemonstratedMembersParamsThe parameters for DemoVaultCoverageChecker.findUndemonstratedMembers.
DemoVaultCoverageCheckerFindUnlinkedFeatureDocsParamsThe parameters for DemoVaultCoverageChecker.findUnlinkedFeatureDocs.
DemoVaultCoverageCheckerFindUnreachableNotesParamsThe parameters for DemoVaultCoverageChecker.findUnreachableNotes.
DemoVaultCoverageCheckerGetExportedFunctionNamesParamsThe parameters for DemoVaultCoverageChecker.getExportedFunctionNames.
DemoVaultCoverageCheckerGetInterfaceMembersParamsThe parameters for DemoVaultCoverageChecker.getInterfaceMembers.
DemoVaultDocsCoverageSpecConfigures the feature-doc linking check.
DemoVaultFunctionsCoverageSpecReflects a module’s exported functions, each demonstrated by its bare name in the demo corpus.
DemoVaultInterfaceCoverageSpecReflects a single interface’s members and demonstrates them via `$\{receiver\}.<member>` references.
DemoVaultNonTrivialGuardSpecConfigures the guard that the reflected surface is non-trivial (protects against a parsing regression silently emptying every other check).
DemoVaultNoteA single demo note, as read from the demo vault.
InterfaceMembersThe parsed members of a source interface, class, or enum.
NoteLineA note line paired with the line number it came from, so a check that skips fenced blocks can still report — and an inline directive can still name — the line as the reader sees it.
OpenWikilinkDirectiveRegionA disable region that has been opened and not yet enabled again.
RegisterDemoVaultCoverageSuiteParamsThe parameters for registerDemoVaultCoverageSuite.
WikilinkDirectiveScanWhat a note’s inline obsidian-dev-utils-disable… directives allow, and what is wrong with them.
WikilinkDirectiveScanStateThe running state of a directive scan, threaded through the per-line helpers.
FunctionDescription
registerDemoVaultCoverageSuiteRegisters a vitest suite that keeps a plugin’s in-repo demo-vault/ in sync with its public surface. Call it once from a plugin’s demo-vault.no-app.integration.test.ts; it registers one test per check, delegating to DemoVaultCoverageChecker.
TypeDescription
DemoVaultInterfaceMemberKindWhether an interface’s demonstrated members are its methods or its properties.