Updated: March 16, 2026
- Overview
- Service Hierarchy
- Core Services
- Plugin-Managed Services
- Supporting Services
- Dependency Injection
- Service Initialization
- Data Flow
- Service Patterns
The service layer hosts business logic that coordinates between the storage system and the React UI. Services handle vault mutations, metadata updates, background processing, and integration points. React components call into services through contexts, hooks, and the public API.
Most UI code accesses services through ServicesContext. The provider exposes the Obsidian app, the plugin instance,
a mobile flag, and the plugin-managed singletons:
fileSystemOps(FileSystemOperations)metadataService(MetadataService)propertyOperations(PropertyOperations)tagOperations(TagOperations)tagTreeService(TagTreeService)propertyTreeService(PropertyTreeService)commandQueue(CommandQueueService)omnisearchService(OmnisearchService)releaseCheckService(ReleaseCheckService)
Convenience hooks wrap ServicesContext access:
useServices()returns the full contextuseFileSystemOps()returnsfileSystemOpsuseMetadataService(),useTagOperations(),usePropertyOperations(), anduseCommandQueue()throw when the service isnull
These instances are created during plugin startup and remain singletons for the lifetime of the plugin.
ServicesProvider throws if fileSystemOps is not initialized; other services are nullable until the plugin finishes
startup and must be guarded. IconService is a global singleton accessed through getIconService().
NotebookNavigatorPluginowns plugin-wide singletons (services, controllers, managers) created duringonload().- Each mounted
NotebookNavigatorViewowns the primary React provider tree.ServicesContextprovides references to plugin singletons, whileStorageProviderowns view-scoped storage state and background queues. - Each mounted
NotebookNavigatorCalendarViewowns a calendar sidebar tree (SettingsProvider→ServicesProvider→CalendarRightSidebar) without storage, selection, or expansion providers. ContentProviderRegistryis created byStorageProviderinside navigator view trees (viauseInitializeContentProviderRegistry) and is stopped during teardown.IndexedDBStorageis initialized once per vault viainitializeDatabase()and is accessed throughgetDBInstance().
StorageContext owns the IndexedDB sync lifecycle and the background content pipeline through ContentProviderRegistry.
The registry is created when a navigator view mounts and is stopped during teardown; it is not exposed through
ServicesContext.
See docs/metadata-pipeline.md (cache rebuild flow, content provider processing pipeline).
graph TB
ServicesContext["ServicesContext<br/>Dependency Injection"]
subgraph "Core Services"
MetadataService["MetadataService<br/>Metadata coordination"]
FileSystemOperations["FileSystemOperations<br/>Vault actions"]
end
subgraph "Supporting Services"
TagTreeService["TagTreeService<br/>Tag tree state"]
PropertyTreeService["PropertyTreeService<br/>Property tree state"]
CommandQueue["CommandQueueService<br/>Operation tracking"]
TagOperations["TagOperations<br/>Tag workflows"]
PropertyOperations["PropertyOperations<br/>Property key workflows"]
Omnisearch["OmnisearchService<br/>Omnisearch bridge"]
ReleaseCheck["ReleaseCheckService<br/>Update notices"]
end
ServicesContext --> MetadataService
ServicesContext --> FileSystemOperations
ServicesContext --> TagTreeService
ServicesContext --> PropertyTreeService
ServicesContext --> CommandQueue
ServicesContext --> TagOperations
ServicesContext --> PropertyOperations
ServicesContext --> Omnisearch
ServicesContext --> ReleaseCheck
graph TB
Plugin["NotebookNavigatorPlugin"]
subgraph "ServicesContext exports"
MetadataService["MetadataService<br/>Metadata coordination"]
FileSystemOperations["FileSystemOperations<br/>Vault actions"]
TagOperations["TagOperations<br/>Tag workflows"]
PropertyOperations["PropertyOperations<br/>Property key workflows"]
TagTreeService["TagTreeService<br/>Tag tree snapshot"]
PropertyTreeService["PropertyTreeService<br/>Property tree snapshot"]
CommandQueue["CommandQueueService<br/>Operation tracking"]
Omnisearch["OmnisearchService<br/>Omnisearch bridge"]
ReleaseCheck["ReleaseCheckService<br/>Update notices"]
end
subgraph "Other plugin services"
RecentNotes["RecentNotesService<br/>Vault-local recents"]
RecentData["RecentDataManager<br/>Vault-local persistence"]
SettingsController["PluginSettingsController<br/>Settings load + sync-mode resolution"]
PreferencesController["PluginPreferencesController<br/>UX prefs + recent data"]
WorkspaceCoordinator["WorkspaceCoordinator<br/>Navigator + calendar leaves"]
HomepageController["HomepageController<br/>Homepage opening"]
ExternalIcons["ExternalIconProviderController<br/>External icon packs"]
API["NotebookNavigatorAPI<br/>Public surface"]
end
Plugin --> MetadataService
Plugin --> FileSystemOperations
Plugin --> TagOperations
Plugin --> PropertyOperations
Plugin --> TagTreeService
Plugin --> PropertyTreeService
Plugin --> CommandQueue
Plugin --> Omnisearch
Plugin --> ReleaseCheck
Plugin --> RecentNotes
Plugin --> RecentData
Plugin --> SettingsController
Plugin --> PreferencesController
Plugin --> WorkspaceCoordinator
Plugin --> HomepageController
Plugin --> ExternalIcons
Plugin --> API
graph TB
MetadataService["MetadataService<br/>Central metadata coordinator"]
subgraph "Sub-Services"
FolderMeta["FolderMetadataService<br/>Colors, icons, sort, appearances"]
TagMeta["TagMetadataService<br/>Colors, icons, sort, appearances"]
PropertyMeta["PropertyMetadataService<br/>Colors, icons, sort overrides"]
FileMeta["FileMetadataService<br/>Pins, icons, colors"]
NavigationSeparators["NavigationSeparatorService<br/>Section/folder/tag/property separators"]
end
MetadataService --> FolderMeta
MetadataService --> TagMeta
MetadataService --> PropertyMeta
MetadataService --> FileMeta
MetadataService --> NavigationSeparators
graph TB
ContentRegistry["ContentProviderRegistry<br/>Provider coordinator"]
subgraph "Content Providers"
MarkdownPipelineProvider["MarkdownPipelineContentProvider<br/>Preview + word count + task counters + property pills + markdown feature images"]
FileThumbnailsProvider["FeatureImageContentProvider<br/>PDF thumbnails + non-markdown processed markers"]
MetadataProvider["MetadataContentProvider<br/>Frontmatter fields + hidden state"]
TagProvider["TagContentProvider<br/>Tag extraction"]
end
ContentRegistry --> MarkdownPipelineProvider
ContentRegistry --> FileThumbnailsProvider
ContentRegistry --> MetadataProvider
ContentRegistry --> TagProvider
ContentProviderRegistry is owned by StorageContext. The registry and its providers are created by
useInitializeContentProviderRegistry when a navigator view mounts and are stopped during teardown. The registry is not
exposed through ServicesContext.
Central coordinator for folder, tag, property, and file metadata. Delegates to specialized sub-services. Reads frontmatter-derived
metadata from IndexedDB when useFrontmatterMetadata is enabled. File icon/color writes and migrations use frontmatter
when useFrontmatterMetadata is enabled.
Location: src/services/MetadataService.ts
Responsibilities:
- Folder metadata: colors, background colors, icons, sort overrides, custom appearances, folder note metadata detection, and folder-note frontmatter writes.
- Tag metadata: colors, background colors, icons, sort overrides, custom appearances.
- Property metadata: key/value node colors, backgrounds, icons, and child sort overrides.
- File metadata: pinned notes, icons, colors, frontmatter writes/migration helpers, iconize conversion support.
- Navigation separators: section, folder, tag, and property separator entries.
- Metadata cleanup and summary reporting based on vault state.
- Rename and delete coordination for folders, tags, and files.
Sub-Services:
-
FolderMetadataService (
src/services/metadata/FolderMetadataService.ts)- Validates folder existence, manages colors/backgrounds/icons, honors inheritance settings.
- Updates metadata paths on rename and delete.
- Cleans stale entries with diff validators.
-
TagMetadataService (
src/services/metadata/TagMetadataService.ts)- Tracks tag colors/backgrounds/icons/sort overrides.
- Updates nested metadata paths on tag rename/delete.
- Resolves inherited tag colors/backgrounds when enabled.
- Uses tag tree snapshots for cleanup.
-
PropertyMetadataService (
src/services/metadata/PropertyMetadataService.ts)- Tracks property key/value colors, backgrounds, icons, and child sort overrides.
- Normalizes property node ids and supports key/value-level metadata.
- Validates settings-backed property metadata against configured property fields.
-
FileMetadataService (
src/services/metadata/FileMetadataService.ts)- Manages pinned notes per folder/tag/property context.
- Stores file icons and colors with frontmatter writes, settings fallback, and migration.
- Updates metadata during file rename/delete and syncs with IndexedDB cache.
-
NavigationSeparatorService (
src/services/metadata/NavigationSeparatorService.ts)- Persists separators for navigation sections, folders, tags, and property nodes.
- Updates separator keys on folder/tag rename and delete.
- Cleans stale entries during metadata cleanup and exposes a versioned subscription.
Selected API surface (examples):
// Folder metadata
setFolderColor(folderPath: string, color: string): Promise<void>
setFolderBackgroundColor(folderPath: string, color: string): Promise<void>
removeFolderColor(folderPath: string): Promise<void>
removeFolderBackgroundColor(folderPath: string): Promise<void>
getFolderColor(folderPath: string): string | undefined
getFolderBackgroundColor(folderPath: string): string | undefined
setFolderIcon(folderPath: string, iconId: string): Promise<void>
removeFolderIcon(folderPath: string): Promise<void>
getFolderIcon(folderPath: string): string | undefined
setFolderStyleChangeListener(listener: ((folderPath: string) => void) | null): void
getFolderDisplayData(folderPath: string): FolderDisplayData
getFolderDisplayVersion(): number
getFolderDisplayNameVersion(): number
setFolderSortOverride(folderPath: string, sortOption: SortOption): Promise<void>
removeFolderSortOverride(folderPath: string): Promise<void>
getFolderSortOverride(folderPath: string): SortOption | undefined
handleFolderRename(oldPath: string, newPath: string): Promise<void>
handleFolderDelete(folderPath: string): Promise<void>
// Tag metadata
setTagColor(tagPath: string, color: string): Promise<void>
setTagBackgroundColor(tagPath: string, color: string): Promise<void>
removeTagColor(tagPath: string): Promise<void>
removeTagBackgroundColor(tagPath: string): Promise<void>
getTagColor(tagPath: string): string | undefined
getTagBackgroundColor(tagPath: string): string | undefined
getTagColorData(tagPath: string): TagColorData
setTagIcon(tagPath: string, iconId: string): Promise<void>
removeTagIcon(tagPath: string): Promise<void>
getTagIcon(tagPath: string): string | undefined
handleTagRename(oldPath: string, newPath: string, preserveExisting?: boolean): Promise<void>
handleTagDelete(tagPath: string): Promise<void>
setTagSortOverride(tagPath: string, sortOption: SortOption): Promise<void>
removeTagSortOverride(tagPath: string): Promise<void>
getTagSortOverride(tagPath: string): SortOption | undefined
setTagChildSortOrderOverride(tagPath: string, sortOrder: AlphaSortOrder): Promise<void>
// Property metadata
setPropertyColor(nodeId: string, color: string): Promise<void>
setPropertyBackgroundColor(nodeId: string, color: string): Promise<void>
removePropertyColor(nodeId: string): Promise<void>
removePropertyBackgroundColor(nodeId: string): Promise<void>
getPropertyColor(nodeId: string): string | undefined
getPropertyBackgroundColor(nodeId: string): string | undefined
getPropertyColorData(nodeId: string): PropertyColorData
setPropertyIcon(nodeId: string, iconId: string): Promise<void>
removePropertyIcon(nodeId: string): Promise<void>
getPropertyIcon(nodeId: string): string | undefined
setPropertyChildSortOrderOverride(nodeId: string, sortOrder: AlphaSortOrder): Promise<void>
removePropertyChildSortOrderOverride(nodeId: string): Promise<void>
getPropertyChildSortOrderOverride(nodeId: string): AlphaSortOrder | undefined
// Navigation separators
getNavigationSeparators(): Record<string, boolean>
hasNavigationSeparator(target: NavigationSeparatorTarget): boolean
addNavigationSeparator(target: NavigationSeparatorTarget): Promise<void>
removeNavigationSeparator(target: NavigationSeparatorTarget): Promise<void>
getNavigationSeparatorsVersion(): number
subscribeToNavigationSeparatorChanges(listener: (version: number) => void): () => void
// File metadata
togglePin(filePath: string, context: NavigatorContext): Promise<void>
pinNotes(filePaths: string[], context: NavigatorContext): Promise<number>
isFilePinned(filePath: string, context?: NavigatorContext): boolean
getPinnedNotes(context?: NavigatorContext): string[]
setFileIcon(filePath: string, iconId: string): Promise<void>
removeFileIcon(filePath: string): Promise<void>
getFileIcon(filePath: string): string | undefined
setFileColor(filePath: string, color: string): Promise<void>
removeFileColor(filePath: string): Promise<void>
getFileColor(filePath: string): string | undefined
setFileBackgroundColor(filePath: string, color: string): Promise<void>
removeFileBackgroundColor(filePath: string): Promise<void>
getFileBackgroundColor(filePath: string): string | undefined
migrateFileMetadataToFrontmatter(): Promise<FileMetadataMigrationResult>
handleFileDelete(filePath: string): Promise<void>
handleFileRename(oldPath: string, newPath: string): Promise<void>
// Cleanup utilities
cleanupAllMetadata(targetSettings?: NotebookNavigatorSettings): Promise<boolean>
cleanupTagMetadata(targetSettings?: NotebookNavigatorSettings): Promise<boolean>
runUnifiedCleanup(validators: CleanupValidators, targetSettings?: NotebookNavigatorSettings): Promise<boolean>
getCleanupSummary(): Promise<MetadataCleanupSummary>
MetadataService.prepareCleanupValidators(app: App, tagTree?: Map<string, TagTreeNode>): CleanupValidatorsHandles all vault mutations triggered from the navigator, including confirmation modals, selection updates, and command queue integration.
Location: src/services/FileSystemService.ts
Responsibilities:
- File and folder creation, rename, deletion, duplication.
- Folder note conversion with conflict handling.
- Tag/property-driven note creation and property assignment.
- Batch file moves with modal workflows and selection updates.
- Canvas/base drawing creation and reveal helpers.
- Command queue tracking for deletes and moves.
- System actions such as reveal in explorer and version history.
Key Methods:
createNewFolder(parent: TFolder, onSuccess?: (path: string) => void): Promise<void>
createNewFile(parent: TFolder): Promise<TFile | null>
createNewFileForTag(tagPath: string, sourcePath?: string, openInNewTab?: boolean): Promise<TFile | null>
createNewFileForProperty(propertyNodeId: string, sourcePath?: string, openInNewTab?: boolean): Promise<TFile | null>
renameFolder(folder: TFolder, settings?: NotebookNavigatorSettings): Promise<void>
renameFile(file: TFile): Promise<void>
deleteFolder(folder: TFolder, confirmBeforeDelete: boolean, onSuccess?: () => void): Promise<void>
deleteFile(file: TFile, confirmBeforeDelete: boolean, onSuccess?: () => void, preDeleteAction?: () => Promise<void>): Promise<void>
deleteSelectedFile(
file: TFile,
settings: NotebookNavigatorSettings,
selectionContext: SelectionContext,
selectionDispatch: SelectionDispatch,
confirmBeforeDelete: boolean
): Promise<void>
duplicateNote(file: TFile): Promise<void>
duplicateFolder(folder: TFolder): Promise<void>
isDescendant(parent: TAbstractFile, child: TAbstractFile): boolean
moveFilesToFolder(options: MoveFilesOptions): Promise<MoveFilesResult>
moveFilesWithModal(
files: TFile[],
selectionContext?: {
selectedFile: TFile | null;
dispatch: SelectionDispatch;
allFiles: TFile[];
}
): Promise<void>
moveFolderWithModal(
folder: TFolder
): Promise<
| { status: 'success'; data: MoveFolderResult }
| { status: 'cancelled' }
| { status: 'error'; error: unknown }
>
convertFileToFolderNote(file: TFile, settings: NotebookNavigatorSettings): Promise<void>
deleteMultipleFiles(
files: TFile[],
confirmBeforeDelete: boolean,
preDeleteAction?: () => void | Promise<void>
): Promise<void>
deleteFilesWithSmartSelection(
selectedFiles: Set<string>,
allFiles: TFile[],
selectionDispatch: SelectionDispatch,
confirmBeforeDelete: boolean
): Promise<void>
createCanvas(parent: TFolder): Promise<TFile | null>
createBase(parent: TFolder): Promise<TFile | null>
createNewDrawing(parent: TFolder, type?: 'excalidraw' | 'tldraw'): Promise<TFile | null>
applyPropertyNodeToFiles(propertyNodeId: string, files: readonly TFile[]): Promise<ApplyPropertyNodeResult>
openVersionHistory(file: TFile): Promise<void>
getRevealInSystemExplorerText(): string
revealInSystemExplorer(file: TFile | TFolder): Promise<void>Coordinates background content providers that populate IndexedDB mirrors used by the UI. Owned by StorageContext.
Location: src/services/content/ContentProviderRegistry.ts
StorageContext creates the registry in useInitializeContentProviderRegistry (src/context/storage/useInitializeContentProviderRegistry.ts).
That hook wires shared helpers used across providers:
ContentReadCache(sharedvault.cachedRead()cache)FeatureImageThumbnailRuntime(shared thumbnail/external-request runtime for feature images)
Responsibilities:
- Provider registration and lookup.
- Settings change coordination, including clearing content and notifying providers.
- Batching queues across providers with optional include/exclude filters.
- Stopping provider processing during teardown.
Registry Methods:
registerProvider(provider: IContentProvider): void
getProvider(type: ContentProviderType): IContentProvider | undefined
getAllProviders(): IContentProvider[]
getAllRelevantSettings(): (keyof NotebookNavigatorSettings)[]
handleSettingsChange(oldSettings: NotebookNavigatorSettings, newSettings: NotebookNavigatorSettings): Promise<ContentProviderType[]>
queueFilesForAllProviders(
files: TFile[],
settings: NotebookNavigatorSettings,
options?: { include?: ContentProviderType[]; exclude?: ContentProviderType[] }
): void
stopAllProcessing(): voidContent Providers:
-
MarkdownPipelineContentProvider (
src/services/content/MarkdownPipelineContentProvider.ts)- Generates markdown-derived content in a single pass (preview text, word count, task counters, property pills, markdown feature images).
- Extends
FeatureImageContentProvider; uses its thumbnail helpers with local files and external references. - Uses Obsidian's metadata cache for frontmatter and frontmatter position offsets.
-
FeatureImageContentProvider (
src/services/content/FeatureImageContentProvider.ts)- Generates PDF thumbnails and records processed markers for other non-markdown files.
- Provides thumbnail helpers used by
MarkdownPipelineContentProvider(local images, external URLs, YouTube).
-
MetadataContentProvider (
src/services/content/MetadataContentProvider.ts)- Extracts configured frontmatter metadata fields and hidden state based on active vault profile hidden frontmatter properties.
-
TagContentProvider (
src/services/content/TagContentProvider.ts)- Extracts tags from Obsidian's metadata cache (
getAllTags(metadata)). - Deduplicates case variants and stores values without the
#prefix.
- Extracts tags from Obsidian's metadata cache (
These services live on the plugin instance and are surfaced to React code through specialized contexts or helper methods.
Location: src/services/RecentNotesService.ts
Responsibilities:
- Maintains the recent notes list stored in vault-local storage.
- Deduplicates entries, enforces configurable limits, and preserves ordering.
- Provides helpers for open, rename, and delete events.
Key Methods:
recordFileOpen(file: TFile): boolean
renameEntry(oldPath: string, newPath: string): boolean
removeEntry(path: string): booleanLocation: src/services/recent/RecentDataManager.ts
Responsibilities:
- Wraps
RecentStorageServicelifecycle for vault-local storage of recent notes and icons. - Hydrates data during plugin startup and notifies listeners on change.
- Applies limits and handles flushing pending persisting tasks.
Key Methods:
initialize(activeVaultProfileId: string): void
dispose(): void
getRecentNotes(): string[]
setRecentNotes(recentNotes: string[]): void
applyRecentNotesLimit(): void
getRecentIcons(): Record<string, string[]>
setRecentIcons(recentIcons: Record<string, string[]>): void
flushPendingPersists(): voidLocation: src/services/settings/PluginSettingsController.ts, src/services/settings/PluginPreferencesController.ts
Responsibilities:
PluginSettingsControllerloadsdata.json, runs synced/local preference migrations, owns sync-mode resolution through the internalsyncModeRegistry, and persists normalized settings.PluginPreferencesControllermanages vault-local UX preferences, dual-pane state mirrors, andRecentDataManagerlifecycle/listeners.
NotebookNavigatorPlugin owns both controllers as long-lived instances. The sync-mode registry still lives in
src/services/settings/syncModeRegistry.ts, but it is created and cached inside PluginSettingsController, not on the
plugin instance directly.
Location: src/services/icons/external/ExternalIconProviderController.ts
Responsibilities:
- Manages install/update/removal flow for external icon packs.
- Downloads assets, stores them in
IconAssetDatabase, and registers providers withIconService. - Syncs provider enablement with plugin settings and bundled manifests.
Key Methods:
initialize(): Promise<void>
dispose(): void
installProvider(id: ExternalIconProviderId, options?: InstallOptions): Promise<void>
removeProvider(id: ExternalIconProviderId, options?: RemoveOptions): Promise<void>
syncWithSettings(): Promise<void>
isProviderInstalled(id: ExternalIconProviderId): boolean
isProviderDownloading(id: ExternalIconProviderId): boolean
getProviderVersion(id: ExternalIconProviderId): string | nullLocation: src/services/workspace/WorkspaceCoordinator.ts
Responsibilities:
- Ensures navigator leaves exist and are revealed.
- Ensures the calendar view leaf is attached to the right sidebar when
calendarPlacementisright-sidebar. - Coordinates manual and contextual reveal actions for files across all navigator instances.
Key Methods:
activateNavigatorView(): Promise<WorkspaceLeaf | null>
getNavigatorLeaves(): WorkspaceLeaf[]
detachCalendarViewLeaves(): void
ensureCalendarViewInRightSidebar(options?: {
reveal?: boolean
activate?: boolean
shouldContinue?: () => boolean
}): Promise<WorkspaceLeaf | null>
revealFileInActualFolder(file: TFile, options?: RevealFileOptions): void
revealFileInNearestFolder(file: TFile, options?: RevealFileOptions): voidLocation: src/services/workspace/HomepageController.ts
Responsibilities:
- Resolves configured homepage files, applies mobile overrides, and opens them at startup or via command.
- Works with
WorkspaceCoordinatorandCommandQueueServiceto open files and reveal them in the navigator. - Handles deferred triggers while the workspace loads.
Key Methods:
resolveHomepageFile(): TFile | null
handleWorkspaceReady(options: { shouldActivateOnStartup: boolean }): Promise<void>
open(trigger: 'startup' | 'command'): Promise<boolean>Location: src/api/NotebookNavigatorAPI.ts
Provides the typed public API surface for external integrations and internal cross-context events.
- Sub-APIs:
navigation,metadata,selection,menus(src/api/modules/*) - Event bus:
on(...),once(...),off(...)(src/api/NotebookNavigatorAPI.ts) - Storage readiness:
isStorageReady()andwhenReady()are public; internal readiness updates use the internal API bridge
Bridge between React storage state and non-React consumers that need tag data.
Location: src/services/TagTreeService.ts
Responsibilities:
- Stores latest tag tree snapshot and tagged/untagged counts.
- Provides lookup helpers used by services outside React.
Key Methods:
updateTagTree(tree: Map<string, TagTreeNode>, tagged: number, untagged: number): void
getTagTree(): Map<string, TagTreeNode>
hasNodes(): boolean
addTreeUpdateListener(listener: () => void): () => void
getFlattenedTagNodes(): readonly TagTreeNode[]
getUntaggedCount(): number
getTaggedCount(): number
findTagNode(tagPath: string): TagTreeNode | null
resolveSelectionTagPath(tagPath: string): string | null
getAllTagPaths(): readonly string[]
collectDescendantTagPaths(tagPath: string): Set<string>
collectTagFilePaths(tagPath: string): string[]Bridge between React storage state and non-React consumers that need property node data.
Location: src/services/PropertyTreeService.ts
Responsibilities:
- Stores latest property tree snapshot keyed by normalized property key.
- Provides property node lookup and selection id resolution.
- Collects descendant node ids and file paths for property key/value nodes.
- Publishes tree update events to consumers outside React.
Key Methods:
updatePropertyTree(tree: Map<string, PropertyTreeNode>): void
getPropertyTree(): Map<string, PropertyTreeNode>
hasNodes(): boolean
addTreeUpdateListener(listener: () => void): () => void
findNode(nodeId: string): PropertyTreeNode | null
getKeyNode(normalizedKey: string): PropertyTreeNode | null
resolveSelectionNodeId(selectionNodeId: PropertySelectionNodeId): PropertySelectionNodeId
collectDescendantNodeIds(nodeId: string): Set<string>
collectFilePaths(nodeId: string, includeDescendants: boolean): Set<string>
collectFilesForKeys(normalizedKeys: Iterable<string>): Set<string>Tracks in-flight operations so React code can batch updates and adjust behavior during complex flows.
Location: src/services/CommandQueueService.ts
Responsibilities:
- Records move/delete operations, folder note opens, version history opens, new context opens, active file opens, and homepage loads.
- Provides
onOperationChangesubscription for UI hooks. - Serializes open-active-file requests and tracks active operation state.
Operation Types:
enum OperationType {
MOVE_FILE = 'move-file',
DELETE_FILES = 'delete-files',
OPEN_FOLDER_NOTE = 'open-folder-note',
OPEN_VERSION_HISTORY = 'open-version-history',
OPEN_IN_NEW_CONTEXT = 'open-in-new-context',
OPEN_ACTIVE_FILE = 'open-active-file',
OPEN_HOMEPAGE = 'open-homepage'
}Key Methods:
onOperationChange(listener: (type: OperationType, active: boolean) => void): () => void
hasActiveOperation(type: OperationType): boolean
getActiveOperations(): Operation[]
clearAllOperations(): void
isMovingFile(): boolean
isDeletingFiles(): boolean
isOpeningFolderNote(): boolean
isOpeningHomepage(): boolean
isOpeningVersionHistory(): boolean
isOpeningInNewContext(): boolean
executeMoveFiles(
files: TFile[],
targetFolder: TFolder
): Promise<CommandResult<{ movedCount: number; skippedCount: number; errors: { filePath: string; error: unknown }[] }>>
executeDeleteFiles(files: TFile[], performDelete: () => Promise<void>): Promise<CommandResult>
executeOpenFolderNote(folderPath: string, openFile: () => Promise<void>): Promise<CommandResult>
executeOpenVersionHistory(file: TFile, openHistory: () => Promise<void>): Promise<CommandResult>
executeOpenInNewContext(file: TFile, context: PaneType, openFile: () => Promise<void>): Promise<CommandResult>
executeOpenActiveFile(file: TFile, openFile: () => Promise<void>): Promise<CommandResult<{ skipped: boolean }>>
executeHomepageOpen(file: TFile, openFile: () => Promise<void>): Promise<CommandResult>Facade for tag operations across the vault.
Location: src/services/TagOperations.ts
Responsibilities:
- Adds tags while preventing duplicates and ancestor conflicts.
- Removes single tags or clears all tags from files.
- Renames and deletes tags using modal workflows and file mutations.
- Updates tag metadata and shortcuts after tag rename/delete operations.
- Emits tag rename/delete events to registered listeners.
Key Methods:
addTagRenameListener(listener: (payload: TagRenameEventPayload) => void): () => void
addTagDeleteListener(listener: (payload: TagDeleteEventPayload) => void): () => void
addTagToFiles(tag: string, files: TFile[]): Promise<{ added: number; skipped: number }>
removeTagFromFiles(tag: string, files: TFile[]): Promise<number>
clearAllTagsFromFiles(files: TFile[]): Promise<number>
getTagsFromFiles(files: TFile[]): string[]
promptRenameTag(tagPath: string): Promise<void>
promoteTagToRoot(sourceTagPath: string): Promise<void>
renameTagByDrag(sourceTagPath: string, targetTagPath: string): Promise<void>
promptDeleteTag(tagPath: string): Promise<void>Facade for property key rename/delete workflows across the vault.
Location: src/services/PropertyOperations.ts
Responsibilities:
- Renames and deletes property keys through modal workflows and batched frontmatter mutations.
- Updates settings-backed property metadata records and active-profile property field lists after successful changes.
- Emits property rename/delete events to listeners used by the React layer and other services.
Key Methods:
addPropertyKeyRenameListener(listener: (payload: PropertyKeyRenameEventPayload) => void): () => void
addPropertyKeyDeleteListener(listener: (payload: PropertyKeyDeleteEventPayload) => void): () => void
promptRenamePropertyKey(normalizedKey: string): Promise<void>
promptDeletePropertyKey(normalizedKey: string): Promise<void>Optional integration with the community Omnisearch plugin.
Location: src/services/OmnisearchService.ts
Responsibilities:
- Detects the Omnisearch API through plugin manager or global scope.
- Executes full-text searches and normalizes result data.
- Registers indexing callbacks when Omnisearch is available.
Key Methods:
isAvailable(): boolean
search(query: string, options?: { pathScope?: string }): Promise<OmnisearchHit[]>
registerOnIndexed(callback: () => void): void
unregisterOnIndexed(callback: () => void): voidGlobal singleton that coordinates icon providers and rendering.
Location: src/services/icons/IconService.ts
Responsibilities:
- Built-in providers (
LucideIconProvider,EmojiIconProvider) are registered duringgetIconService()initialization. - Manages provider registry, icon id parsing/formatting, rendering, validation, and search.
ExternalIconProviderControllerregisters downloadable providers withIconService.
Icon Providers:
- Built-in:
LucideIconProvider,EmojiIconProvider - Downloadable:
BootstrapIconProvider,FontAwesomeIconProvider,MaterialIconProvider,PhosphorIconProvider,RpgAwesomeIconProvider,SimpleIconsProvider
Key Methods:
registerProvider(provider: IconProvider): void
unregisterProvider(providerId: string): void
getProvider(providerId: string): IconProvider | undefined
getAllProviders(): IconProvider[]
parseIconId(iconId: string): ParsedIconId
formatIconId(provider: string, identifier: string): string
renderIcon(container: HTMLElement, iconId: string, size?: number): void
isValidIcon(iconId: string): boolean
search(query: string, providerId?: string): IconDefinition[]
getAllIcons(providerId?: string): IconDefinition[]
getVersion(): number
subscribe(listener: () => void): () => voidLocation: src/services/ReleaseCheckService.ts
Responsibilities:
- Polls GitHub releases and compares versions with the installed plugin.
- Stores timestamps and latest announced releases in settings.
- Provides pending notices consumed by the React UI.
Key Methods:
checkForUpdates(force?: boolean): Promise<ReleaseUpdateNotice | null>
getPendingNotice(): ReleaseUpdateNotice | null
clearPendingNotice(): voidMany storage and metadata helpers depend on small provider interfaces (ISettingsProvider, ITagTreeProvider,
IPropertyTreeProvider), while plugin/workspace controllers still depend on NotebookNavigatorPlugin or concrete
services.
Location: src/interfaces/ISettingsProvider.ts
interface ISettingsProvider {
readonly settings: NotebookNavigatorSettings;
saveSettingsAndUpdate(): Promise<void>;
notifySettingsUpdate(): void;
getRecentNotes(): string[];
setRecentNotes(recentNotes: string[]): void;
getRecentIcons(): Record<string, string[]>;
setRecentIcons(recentIcons: Record<string, string[]>): void;
getRecentColors(): string[];
setRecentColors(recentColors: string[]): void;
}Location: src/interfaces/ITagTreeProvider.ts
interface ITagTreeProvider {
addTreeUpdateListener(listener: () => void): () => void;
hasNodes(): boolean;
findTagNode(tagPath: string): TagTreeNode | null;
resolveSelectionTagPath(tagPath: string): string | null;
getAllTagPaths(): readonly string[];
collectDescendantTagPaths(tagPath: string): Set<string>;
collectTagFilePaths(tagPath: string): string[];
}Location: src/interfaces/IPropertyTreeProvider.ts
interface IPropertyTreeProvider {
addTreeUpdateListener(listener: () => void): () => void;
hasNodes(): boolean;
findNode(nodeId: string): PropertyTreeNode | null;
getKeyNode(normalizedKey: string): PropertyTreeNode | null;
resolveSelectionNodeId(selectionNodeId: PropertySelectionNodeId): PropertySelectionNodeId;
collectDescendantNodeIds(nodeId: string): Set<string>;
collectFilePaths(nodeId: string, includeDescendants: boolean): Set<string>;
collectFilesForKeys(normalizedKeys: Iterable<string>): Set<string>;
}Location: src/interfaces/IContentProvider.ts
interface IContentProvider {
getContentType(): ContentProviderType;
getRelevantSettings(): (keyof NotebookNavigatorSettings)[];
shouldRegenerate(oldSettings: NotebookNavigatorSettings, newSettings: NotebookNavigatorSettings): boolean;
clearContent(context?: ContentProviderClearContext): Promise<void>;
queueFiles(files: TFile[]): void;
startProcessing(settings: NotebookNavigatorSettings): void;
stopProcessing(): void;
waitForIdle(): Promise<void>;
onSettingsChanged(settings: NotebookNavigatorSettings): void;
}Services are instantiated during plugin startup (see docs/startup-process.md, phase 1). The current initialization
sequence in src/main.ts is:
// Database and settings load happen before this block
this.preferencesController.initializeRecentDataManager();
this.recentNotesService = new RecentNotesService(this);
// Initialize workspace and homepage coordination
this.workspaceCoordinator = new WorkspaceCoordinator(this);
this.homepageController = new HomepageController(this, this.workspaceCoordinator);
// Initialize services
this.tagTreeService = new TagTreeService();
this.propertyTreeService = new PropertyTreeService();
this.metadataService = new MetadataService(
this.app,
this,
() => this.tagTreeService,
() => this.propertyTreeService
);
this.tagOperations = new TagOperations(
this.app,
() => this.settings,
() => this.tagTreeService,
() => this.metadataService
);
this.propertyOperations = new PropertyOperations(
this.app,
() => this.settings,
() => this.saveSettingsAndUpdate(),
() => this.propertyTreeService
);
this.commandQueue = new CommandQueueService();
this.fileSystemOps = new FileSystemOperations(
this.app,
() => this.tagTreeService,
() => this.propertyTreeService,
() => this.commandQueue,
() => this.metadataService,
(): VisibilityPreferences => ({
includeDescendantNotes: this.preferencesController.getUXPreferences().includeDescendantNotes,
showHiddenItems: this.preferencesController.getUXPreferences().showHiddenItems
}),
this
);
this.omnisearchService = new OmnisearchService(this.app);
if (this.settings.searchProvider === 'omnisearch' && !this.omnisearchService.isAvailable()) {
this.setSearchProvider('internal');
}
this.api = new NotebookNavigatorAPI(this, this.app);
this.metadataService.setFolderStyleChangeListener(folderPath => {
if (!this.api) {
return;
}
this.api[INTERNAL_NOTEBOOK_NAVIGATOR_API].metadata.emitFolderChangedForPath(folderPath);
});
this.releaseCheckService = new ReleaseCheckService(this);
const iconService = getIconService();
iconService.registerProvider(new VaultIconProvider(this.app));
this.externalIconController = new ExternalIconProviderController(this.app, iconService, this);
const iconController = this.externalIconController;
if (iconController) {
runAsyncAction(
async () => {
await iconController.initialize();
await iconController.syncWithSettings();
},
{
onError: (error: unknown) => {
console.error('External icon controller init failed:', error);
}
}
);
}React mounts the navigator with dependency injection:
<SettingsProvider plugin={plugin}>
<UXPreferencesProvider plugin={plugin}>
<RecentDataProvider plugin={plugin}>
<ServicesProvider plugin={plugin}>
<ShortcutsProvider>
<StorageProvider app={plugin.app} api={plugin.api}>
<ExpansionProvider>
<SelectionProvider /* app/api/file rename wiring */>
<UIStateProvider isMobile={isMobile}>
<NotebookNavigatorContainer />
</UIStateProvider>
</SelectionProvider>
</ExpansionProvider>
</StorageProvider>
</ShortcutsProvider>
</ServicesProvider>
</RecentDataProvider>
</UXPreferencesProvider>
</SettingsProvider>StorageProvider creates the ContentProviderRegistry during mount and registers all providers. View-scoped contexts
(ExpansionProvider, SelectionProvider, UIStateProvider) own React state and are not service singletons. Processing
starts when StorageContext queues files through ContentProviderRegistry.queueFilesForAllProviders(...) (which calls
startProcessing() for each provider).
- File Detection:
StorageContextpicks files needing processing and callsContentProviderRegistry.queueFilesForAllProviders(...)(optionally with include/exclude filters). Providers skip files without enabled features and dedupe the queue. - Queue Management:
BaseContentProviderbatches up to 100 files and processes them with a parallel limit of 10, with debounce timers, abort signals, and retry scheduling. - Processing: Providers read vault data and produce updates:
- Tags and metadata via
app.metadataCache.getFileCache()(retries when cache data is missing) - Preview text via
ContentReadCache/app.vault.cachedRead()(skips oversized markdown reads viaLIMITS.markdown.maxReadBytes) - Feature images via frontmatter properties, markdown body references, and attachment thumbnails (local + optional external URLs)
- Metadata via
extractMetadataFromCacheand hidden-state checks against active vault profile hidden frontmatter properties
- Tags and metadata via
- Database Updates: Providers persist updates via
IndexedDBStorage.batchUpdateFileContentAndProviderProcessedMtimes(...)and clear content viabatchClearAllFileContent/batchClearFeatureImageContent. - Memory Sync:
MemoryFileCachemirrors IndexedDB updates for synchronous access. - UI Updates:
StorageContextlistens for database events, rebuilds tag/property trees, and updates React contexts.
Triggered manually from Settings → Notebook Navigator → Advanced → Clean up metadata.
- Preview: settings UI calls
NotebookNavigatorPlugin.getMetadataCleanupSummary()which runsMetadataService.getCleanupSummary()against a cloned settings object. - Cleanup Execution: settings UI calls
NotebookNavigatorPlugin.runMetadataCleanup()which runsMetadataService.cleanupAllMetadata()(folder/tag/property/file/pinned note/separator cleanup). - Persistence:
NotebookNavigatorPlugin.runMetadataCleanup()callssaveSettingsAndUpdate()when changes are detected. - Feedback: settings UI can re-run
getMetadataCleanupSummary()after cleanup to confirm counts.
- User initiates an operation (create, rename, move, delete, duplicate).
FileSystemOperationsgathers input through modals when required.CommandQueueServicewraps operations that coordinate workspace state (moves, deletes, folder note opens, version history opens, new-context opens, active file opens, homepage opens).- Vault mutations execute through
app.fileManager(rename/move/trash/frontmatter) andapp.vault(creates/reads). - Selection state updates via helpers from
selectionUtils. StorageContextobserves vault events, records changes into IndexedDB, and rebuilds tag and property trees.- React contexts broadcast changes to the UI.
- Settings UI updates values and persists via
saveSettingsAndUpdate()(synced settings) or local preference helpers. NotebookNavigatorPluginnotifies settings listeners (registerSettingsUpdateListener) and open React views.StorageContextreceives new settings and callsContentProviderRegistry.handleSettingsChange(old, new)to stop providers, wait for idle batches, clear content when required, and re-queue work.- Controllers that mirror settings state (for example
ExternalIconProviderController) resync based on updated settings.
NotebookNavigatorPlugininitializesIconServiceviagetIconService()and createsExternalIconProviderController.ExternalIconProviderController.initialize()loads persisted provider state and prepares the icon asset database.ExternalIconProviderController.syncWithSettings()installs/removes providers based on settings and updates enablement.IconServicepublishes version changes; React code can subscribe viauseIconServiceVersion().
registerWorkspaceEventswires Obsidian events (context menus, file-open, vault rename/delete).- File opens update recent notes via
RecentNotesService.recordFileOpen. - Vault renames/deletes update metadata (
MetadataService.handle*), recent note paths, hidden folder exact-match settings, and selection context file path tracking. - Active-file auto-reveal after a rename skips moves initiated by the navigator (
CommandQueueService.isMovingFile()).
- Singleton:
IconServiceand plugin-managed services are singleton instances; each mountedStorageProviderowns aContentProviderRegistry. - Provider:
ContentProviderRegistryandIconServicemanage lists of registered providers. - Delegation:
MetadataServicedelegates specialized work to folder/tag/property/file/separator sub-services. - Bridge:
TagTreeServiceandPropertyTreeServicebridge StorageContext data to non-React consumers. - Observer:
CommandQueueService,IconService, recent data listeners, andsubscribeToNavigationSeparatorChanges()publish state changes to the UI. - Facade:
TagOperationsandPropertyOperationswrap vault-wide rename/delete workflows. - Controller:
WorkspaceCoordinator,HomepageController, andExternalIconProviderControllercoordinate multi-step plugin flows.