Architecture#

Source layout#

All of the plugin’s logic lives under src/:

File

Responsibility

main.ts

Plugin entry point: loads settings, registers the sidebar/month views, the editor suggest, the live preview extension, and the post-processor; adds the Open calendar in sidebar, Open calendar in a new tab, and Refresh calendars commands.

types.ts

Shared types: CalDavEvent, CalDavCalendar, CalDavAccount, CalDavTimezone, HarangCalendarSettings, CalendarScope, HrcalScope, NoteEvent, CalendarListItem.

settings.ts / settingsTab.ts

Default settings and the settings UI. settingsTab.ts implements Obsidian’s newer declarative getSettingDefinitions() API (1.13.0+) exclusively, with no legacy imperative display() fallback - safe because the plugin’s declared minAppVersion is 1.13.4.

i18n.ts

Looks up the current Obsidian UI language via the official getLanguage() API and returns matching strings from a small ko/en dictionary (falling back to English).

caldav/client.ts

A minimal read-only CalDAV client: calendar discovery (PROPFIND/current-user-principal/calendar-home-set) and event fetching (REPORT calendar-query, optionally windowed to a time range), over Obsidian’s requestUrl.

caldav/ics.ts

An iCalendar (RFC 5545) parser extracting VEVENT properties into a CalDavEvent, skipping VALARM/other sub-components so their properties don’t leak into the parent event.

caldav/recurrence.ts

Expands a CalDavEvent with an RRULE into concrete occurrences within a range, via the rrule package (EXDATE entries excluded).

caldav/timezone.ts

Resolves an account’s configured time zone (an IANA zone name via Intl, or a fixed UTC offset) and lists known IANA zones for the settings dropdown.

caldav/store.ts

CalendarStore: merges and caches events from every configured account/calendar, with TTL-based staleness, on-demand gap-filling for the month view’s range navigation, and scope/search/lookup helpers.

editorSuggest/HrcalEditorSuggest.ts, dateCandidates.ts

Triggers on {{hrcal:. A single staged suggester (no separate @date[/@event[ triggers): it splits the typed query on : to figure out which stage it’s in - account name, then that account’s calendar name, then a combined date-candidate/event-title search - and inserts {{hrcal:<accountName>:<calendarName>:date:YYYY-MM-DD}} or {{hrcal:<accountName>:<calendarName>:event:<uid>}}.

render/frontmatterScope.ts

Reads a note’s harang-account/harang-calendar frontmatter into a CalendarScope. Currently unused - nothing calls this since {{hrcal:...}} references name their account/calendar directly instead of relying on note frontmatter.

notes/noteEvents.ts

Scans the vault for harang-date/harang-repeat/harang-time frontmatter and expands each into NoteEvent occurrences inside a range - purely local to Obsidian, unrelated to any CalDAV server. Re-derives its own all-day-safe RRULE expansion rather than reusing caldav/recurrence.ts (see below). Unrelated to frontmatterScope.ts above - different frontmatter keys, different purpose.

render/dateWidget.ts

Builds the {{hrcal:...:date:YYYY-MM-DD}} card widget: a heading, a warning if the embedded account/calendar name doesn’t match a registered one, and that day’s events from that specific account/calendar as clickable rows.

render/eventChip.ts / render/eventCard.ts

The inline event chip, and the click-to-open detail popup (a manually positioned floating panel, closed on outside click or Esc). The lookup is scoped to the accountName/calendarName embedded in the {{hrcal:<accountName>:<calendarName>:event:<uid>}} reference itself - the reference already names one exact event.

render/livePreview.ts

A CodeMirror 6 ViewPlugin that replaces {{hrcal:...}} ranges (both date and event kinds, via one regex) with widget/chip widgets in Live Preview, skipping ranges the cursor or selection currently overlaps so the raw syntax stays editable.

render/postProcessor.ts

A Markdown post-processor that does the same replacement for Reading view, by scanning rendered text nodes directly - {{...}} isn’t wikilink syntax, so unlike the old [[cal:...]]/[[event:...]] syntax it’s never pre-parsed into an a.internal-link by Obsidian, and Live Preview and Reading view now share the exact same raw-text matching path.

view/agenda.ts, view/AgendaItemView.ts, vue/AgendaView.vue

The sidebar agenda list: a fixed 30-day window of CalendarListItem (CalDAV events and note events merged) grouped by local day, rendered by a Vue 3 component the ItemView hosts.

view/monthGrid.ts, view/MonthItemView.ts, vue/MonthView.vue

The full-tab month grid view (same CalendarListItem merge as the agenda list), including keyboard (arrow-key) date navigation and cross-month boundary handling.

view/MonthPickerModal.ts

The year/month picker opened from the month view’s heading, with a click-to-edit year field.

Data flow#

settingsTab.ts          -->  CalDavAccount[] (server URL, credentials, calendars)
     |
     v
caldav/client.ts         -->  PROPFIND/REPORT over requestUrl
     |                        (discovery + event fetch)
     v
caldav/ics.ts              -->  parses each VEVENT into a CalDavEvent
     |
     v
caldav/recurrence.ts         -->  expands RRULE occurrences on demand
     |
     v
caldav/store.ts                -->  merged, cached, scoped CalDavEvent[]
     |
     +--> editorSuggest/*.ts          -->  autocomplete while typing
     |
     +--> render/livePreview.ts       -->  widget/chip (Live Preview)
     |
     +--> render/postProcessor.ts     -->  widget/chip (Reading view)
     |        |
     |        v
     |  render/eventChip.ts + eventCard.ts  -->  click-to-open detail popup
     |
     +--> view/AgendaItemView.ts / vue/AgendaView.vue   -->  sidebar list
     |
     +--> view/MonthItemView.ts / vue/MonthView.vue     -->  month grid

notes/noteEvents.ts (vault-wide frontmatter scan) --> NoteEvent[]
     |
     +--> (merged into a CalendarListItem[] alongside CalDavEvent[],
           inside AgendaItemView.ts/MonthItemView.ts, right before
           the sidebar list / month grid above render - nothing else
           in this diagram ever sees a NoteEvent)

Reference syntax#

{{hrcal:<accountName>:<calendarName>:date:YYYY-MM-DD}} renders as a date widget; {{hrcal:<accountName>:<calendarName>:event:<uid>}} renders as an event chip. Both are normally inserted by the staged {{hrcal: editor suggest, but either can be typed by hand too — an unresolvable reference renders as a faded, dashed chip (or, for a date whose account/calendar doesn’t match a registered one, a warning inside the widget) instead of failing silently.

Both kinds are always scoped by the accountName/calendarName named directly in the reference - note frontmatter (harang-account/ harang-calendar, see Usage) plays no role here any more.

Note events (harang-date/harang-repeat/harang-time)#

Unrelated to the reference syntax above: a note carrying harang-date frontmatter (see Usage) becomes a NoteEvent and shows up as an item in the sidebar agenda list and month grid only - never in a {{hrcal:...}} date widget or as an event chip, and never written to or read from a CalDAV server. harang-repeat reuses the same rrule package as caldav/recurrence.ts, but with its own from-scratch expansion logic (see notes/noteEvents.ts above) rather than sharing that module’s function directly, since a note event has no time-of-day at all by default and needs consistently UTC-midnight-anchored date math throughout to avoid the local/UTC all-day mismatch this project has hit (and fixed) several times elsewhere. harang-time layers an optional HH:MM-HH:MM display range on top of that all-day date math - it only affects how a NoteEvent is labeled and sorted alongside timed CalDAV events, not the (still UTC-midnight-anchored) date expansion itself.

No runtime dependencies beyond rrule#

Beyond what Obsidian itself provides (the obsidian package, CodeMirror 6, Vue 3) and the rrule package for recurrence expansion, the plugin has no runtime dependencies — the CalDAV client and iCalendar parser are both hand-written rather than pulled in from npm, keeping the bundle small and avoiding exposure to a third-party HTTP/XML parsing library’s own vulnerabilities.