Architecture
This page covers the internal architecture for developers who want to understand how the plugin works or contribute to it.
Component overview
Section titled “Component overview”Plugin (main.ts) ├── TrackerRegistry Map<string, TrackerModule> ├── NotificationService Periodic vault scanner + multi-channel dispatch ├── TodoistService Todoist API v1 integration ├── StatusBarManager Status bar display (badge/live/off) └── Code block processor └── TrackerWidget MarkdownRenderChild ├── BabyInfoBar ├── DailySummary ├── QuickActions ├── AlertsPanel ├── QuickEntrySection NLP text input + preview ├── CollapsibleSection[] (one per module, reorderable) │ └── module.buildUI() └── EventHistorySection Unified chronological feed (in layout)Plugin lifecycle
Section titled “Plugin lifecycle”-
onload()— Registers thepostpartum-trackercode block processor, creates theTrackerRegistry, registers all enabled modules (3 core + library modules fromsettings.enabledModules), startsNotificationServiceandTodoistService. -
Code block processing — When Obsidian encounters a
postpartum-trackercode block, the processor creates a newTrackerWidgetinstance, passing the raw JSON source. -
Widget rendering —
TrackerWidgetextendsMarkdownRenderChild. It parses the JSON viaCodeBlockStore.parse(), iterates the registry to initialize each module with its data, and builds the UI. -
Tick loop — A 200ms
setIntervalcallsmodule.tick()on each module for live timer updates. -
Save cycle — When any module calls
save(), the widget serializes all module data, writes the updated JSON back to the code block viaCodeBlockStore.save(), which triggers Obsidian to re-render the entire code block (creating a newTrackerWidget).
TrackerModule interface
Section titled “TrackerModule interface”Every tracker implements this interface (defined in src/trackers/BaseTracker.ts):
interface TrackerModule<E, S> { readonly id: string; readonly displayName: string; readonly defaultExpanded: boolean; readonly defaultOrder: number;
// Data marshaling parseEntries(raw: unknown): E[]; serializeEntries(): E[]; emptyEntries(): E[]; update(entries: E[]): void;
// UI buildUI(bodyEl: HTMLElement, save: () => Promise<void>, settings: PostpartumTrackerSettings, emitEvent: (event: TrackerEvent) => void): void;
// Dashboard getQuickActions(): QuickAction[]; computeStats(entries: E[], dayStart: Date, dayEnd: Date): S; renderSummary(el: HTMLElement, stats: S): void;
// Optional tick?(): void; getAlerts?(): HealthAlert[]; editEntry?(id: string): void; deleteEntry?(id: string): Promise<void>; addEntry?(data: Record<string, unknown>): void; // NLP quick entry
// Library metadata (optional) category?: TrackerCategory; icon?: string; description?: string; isSmart?: boolean;}Module categories
Section titled “Module categories”-
Core modules (feeding, diaper, medication) — Dedicated
TrackerModuleclasses with custom UI, complex state management, deep notification integration, and full Todoist task lifecycle. -
Library modules (sleep, pumping, pain, mood, etc.) — All instantiated from a single
SimpleTrackerModuleclass using data-drivenSimpleTrackerDefdefinitions. No custom code per tracker.
Data flow
Section titled “Data flow”User taps button -> module.logEntry() -> entries.push(entry) -> emitEvent('feeding-logged', entry) -> save() -> for each module: data.trackers[id] = module.serializeEntries() -> CodeBlockStore.save(ctx, containerEl, data) -> app.vault.process(file, content => ...) -> Obsidian detects change, re-renders code block -> new TrackerWidget created with updated JSONThe save is atomic — it uses ctx.getSectionInfo() to locate the code block boundaries within the file, then app.vault.process() to rewrite just that section.
Event system
Section titled “Event system”A simple Map-based event bus on the plugin instance:
emitTrackerEvent(event)— Fires to all listeners forevent.typeonTrackerEvent(type, listener)— Registers a listener
Event types: feeding-logged, medication-logged, diaper-logged, simple-logged, comment-logged, todoist-entry-created
Events are used by:
- TodoistService — Creates proactive tasks after logging
- NotificationService — Triggers immediate checks
CodeBlockStore
Section titled “CodeBlockStore”src/data/CodeBlockStore.ts handles persistence:
parse(source)— JSON string toPostpartumData. Applies defaults, backward compatibility, and preserves arbitrary tracker keys (so library module data is never silently dropped).save(ctx, containerEl, data)— Serializes to JSON and writes back to the code block usingctx.getSectionInfo()+app.vault.process().
Key patterns
Section titled “Key patterns”Event handling in code blocks
Section titled “Event handling in code blocks”CodeMirror 6 (Live Preview mode) intercepts standard click events on elements inside code blocks. The plugin uses pointerdown/pointerup with preventDefault() + stopImmediatePropagation() to ensure buttons work reliably.
Save triggers full re-render
Section titled “Save triggers full re-render”Every save rewrites the code block JSON, which causes Obsidian to destroy the current widget and create a new one. Modules must not hold references to DOM elements across saves.
Entry sorting and display
Section titled “Entry sorting and display”Entries are always sorted by timestamp after adding or editing. Entry lists display newest first with day-separator headers (“Today”, “Yesterday”, “Mon, Mar 3”).
File structure
Section titled “File structure”src/ main.ts Plugin entry, code block processor types.ts All interfaces and defaults settings.ts Settings tab StatusBarManager.ts Status bar display data/ CodeBlockStore.ts JSON persistence TrackerRegistry.ts Module registration dateUtils.ts Day boundary helpers nlp/ QuickEntryParser.ts Rules-based NLP parser trackers/ BaseTracker.ts TrackerModule interface library.ts SimpleTrackerDef catalog logicPacks.ts Logic pack milestone definitions milestoneEvaluator.ts Milestone rule engine feeding/FeedingTracker.ts Core: feeding feeding/feedingStats.ts diaper/DiaperTracker.ts Core: diapers diaper/diaperStats.ts medication/MedicationTracker.ts Core: medication medication/medicationStats.ts comment/CommentTracker.ts Core: notes & comments simple/SimpleTrackerModule.ts Library: generic module class simple/simpleTrackerStats.ts widget/ TrackerWidget.ts Main widget (MarkdownRenderChild) CollapsibleSection.ts Reorderable sections QuickActions.ts Button grid QuickEntrySection.ts NLP quick entry UI DailySummary.ts Summary strip AlertsPanel.ts Health alerts EventHistorySection.ts Unified event feed shared/ BigButton.ts Touch-friendly buttons TimerDisplay.ts Live timer component EntryList.ts Scrollable entry list InlineEditPanel.ts Inline edit forms FieldRenderer.ts Dynamic field rendering ui/ TrackerEditModal.ts Modal edit dialog integrations/ TodoistService.ts Todoist API v1 notifications/ NotificationService.ts Periodic scanner + multi-channel dispatch ToastNotification.ts In-app toast UI utils/ dom.ts DOM helpers formatters.ts Time/date formatting deepMerge.ts Settings merge utility