Skip to content

Architecture

Module map

graph TD
Plugin["DailyNotesNGPlugin<br/><i>src/main.ts</i>"]
Journals["settings.journals: JournalDefinition[]<br/>name, periodicity, folder, template, scope, owner"]
Plugin --> Journals
Plugin --> PM["PeriodicNoteManager<br/>Creates/opens notes per journal"]
Plugin --> CV["CalendarView<br/>Month grid sidebar"]
Plugin --> Nav["NavigationCommands<br/>Journal picker when multiple match"]
PM --> JR["JournalResolver<br/>Filters journals by scope for current device/user"]
JR --> DIM["DeviceIdentityManager"]
JR --> PNS["PersonNoteService"]
JR --> UR["UserRegistry<br/>device-to-user mapping"]
JR --> GR["GroupRegistry<br/>group members"]
Plugin --> TE["TemplateEngine<br/>built-in vars"]
Plugin --> TB["TemplaterBridge<br/>delegates when trigger is on"]
Plugin --> TR["TodoRollover"]
Plugin --> FT["FrontmatterDateTracker"]
Plugin --> DS["DateSuggest<br/>EditorSuggest"]
style Plugin fill:#7c3aed,color:#fff
style Journals fill:#2d1854,color:#c4b5fd
style JR fill:#5b21b6,color:#fff

Data flow: creating a periodic note

When a user triggers “Open today’s daily note”:

sequenceDiagram
actor User
participant Nav as NavigationCommands
participant JR as JournalResolver
participant Picker as JournalPickerModal
participant PM as PeriodicNoteManager
participant TE as Template Engine
User->>Nav: Open today's daily note
Nav->>JR: getJournalsForPeriodicity('daily')
JR-->>Nav: available journals
alt 1 journal
Nav->>PM: openPeriodicNote(date, journal)
else 2+ journals
Nav->>Picker: show picker
Picker-->>Nav: user picks one
Nav->>PM: openPeriodicNote(date, journal)
end
PM->>JR: resolveFolder(journal)
Note over JR: Interpolates person in path
JR-->>PM: resolved folder path
alt Note exists
PM->>PM: ensure .base MOC
PM->>User: open note
else New note
PM->>PM: create folder
PM->>TE: process template
PM->>PM: add creator + UUID
PM->>User: open note
end

Journal resolution flow

flowchart TD
Input["All journals in settings"] --> Enabled{"Enabled?"}
Enabled -->|No| Skip["Skip"]
Enabled -->|Yes| Scope{"Scope?"}
Scope -->|Global| Include["Include"]
Scope -->|Person| PersonCheck{"ownerPath matches<br/>current user?"}
Scope -->|Group| GroupCheck{"Current user in<br/>group members?"}
PersonCheck -->|Yes| Include
PersonCheck -->|No| Skip
GroupCheck -->|Yes| Include
GroupCheck -->|No| Skip
Scope -->|Identity disabled| GlobalOnly["Only global journals"]
Include --> Output["Available journals<br/>for this device"]
GlobalOnly --> Output
style Input fill:#2d1854,color:#c4b5fd
style Output fill:#7c3aed,color:#fff
style Skip fill:#991b1b,color:#fff

Storage layers

LayerWhereSynced?Contains
localStorageBrowserNoDevice ID, device preferences
Plugin settingsdata.jsonYesJournal definitions, device-to-user mappings, all config
Person note frontmatterPeople/Alice.mdYesPerson preferences, timezone
Group note frontmatterPeople/Dev Team.mdYesGroup membership list
Note frontmatterAny .md fileYesNote UUID, creator, dates
.base MOCJournal folderYesPortable Bases query for folder contents

Key design decisions

Why named journals instead of one-per-periodicity? Users need multiple daily journals (personal, work, team). The old model forced everything into one folder. Named journals let each context have its own destination.

Why scope-based visibility? In a shared vault, Alice shouldn’t see Bob’s personal journal in her command palette. Scopes ensure each device only shows relevant journals.

Why {{person}} interpolation? For the common case where journals share the same structure per person. Journal/{{person}}/Daily avoids duplicating journal definitions for each user.

Why auto-migration? Users upgrading from the old per-periodicity config shouldn’t lose their settings. The migration converts each enabled periodicity to a global journal.

Why a journal picker instead of per-journal commands? Dynamically registering a command for each journal would clutter the command palette. The picker appears only when disambiguation is needed.