Calendar Cockpit (Sidebar) Architecture¶
This document details the architectural design, component lifecycles, state persistence, and extensibility patterns of the Calendar Cockpit (src/features/calendar-sidebar/).
Architectural Principles (SOLID & DRY)¶
The Cockpit is architected around strict separation of concerns, decoupling presentation cards from the core FullCalendar engine:
┌────────────────────────────────────────────────────────────────────────┐
│ CalendarView (Obsidian ItemView) │
│ Container: [ .ofc-calendar-shell ] │
└────────────────────────────────────┬───────────────────────────────────┘
│
┌───────────────────┴───────────────────┐
▼ ▼
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│ CalendarSidebarManager │ │ ofc-calendar-container │
│ (src/features/calendar-sidebar)│ │ (FullCalendar Instance) │
└────────────────┬────────────────┘ └────────────────┬────────────────┘
│ │
│◀──────────────[Resizer]─────────────▶│
│ Divider │
▼ ▼
┌─────────────────────────────────┐ ┌─────────────────────────────────┐
│ SidebarModuleRegistry │ │ fullCalendarView.updateSize() │
└────────────────┬────────────────┘ └─────────────────────────────────┘
│
┌─────────────┼─────────────┬─────────────┐
▼ ▼ ▼ ▼
┌──────────────┐ ┌───────────┐ ┌─────────────┐ ┌──────────────┐
│MiniMonth │ │UpNext │ │TaskBacklog │ │AgentHud │
│Module │ │Module │ │Module │ │Module │
└──────────────┘ └───────────┘ └─────────────┘ └──────────────┘
1. SOLID Design Analysis¶
- Single Responsibility (SRP):
CalendarSidebarManager: Manages lifecycle mounting, responsive visibility, and container coordination.SidebarResizer: Encapsulates pointer events, clamping, and splitter DOM states.SidebarStateStore: Encapsulates reading, writing, clamping, and debouncing configuration changes to Obsidian vault storage.CollapsibleSection: Generic DRY UI wrapper handling accordion folding, chevrons, titles, and action slots.- Domain modules (
MiniMonthModule,UpNextWeatherModule,TaskBacklogModule,AgentHudModule): Each module is strictly responsible for its own data rendering and user interaction.
- Open / Closed (OCP):
- New modules register via
registry.registerModuleFactory(id, factory)without requiring any modifications to the core manager or existing modules.
- New modules register via
- Liskov Substitution (LSP):
- The registry interacts with all cards uniformly through the
ISidebarModulecontract. Modules can be swapped, disabled, or reordered dynamically.
- The registry interacts with all cards uniformly through the
- Interface Segregation (ISP):
ISidebarModulerequires onlymount(containerEl, context)anddestroy(). Notification hooks (onDateChange,onDateRangeChange,onCacheUpdate) are completely optional.
- Dependency Inversion (DIP):
- Modules do not access the parent
CalendarViewDOM directly; they interact through the abstractedSidebarContextinterface providing navigation and sizing APIs.
- Modules do not access the parent
Component Lifecycle & Coordination¶
sequenceDiagram
autonumber
participant View as CalendarView
participant Shell as ofc-calendar-shell
participant Manager as CalendarSidebarManager
participant Resizer as SidebarResizer
participant Registry as SidebarModuleRegistry
participant Modules as ISidebarModule Instances
participant Store as SidebarStateStore
View->>Manager: new CalendarSidebarManager(options)
View->>Manager: addChild(manager)
Manager->>Store: getSettings()
Store-->>Manager: { width: 290, isCollapsed: false, moduleOrder: [...] }
Manager->>Shell: style.setProperty('--sidebar-width', '290px')
Manager->>Shell: insertBefore(sidebarEl, calendarEl)
Manager->>Resizer: new SidebarResizer(...)
Manager->>Shell: insertBefore(resizerEl, calendarEl)
Manager->>Registry: mountAll(scrollContainer, context)
loop For each module in moduleOrder
Registry->>Modules: factory() -> mount(container, context)
end
Note over View,Modules: Interactive Execution Phase
View->>Manager: notifyDateChange(date)
Manager->>Registry: notifyDateChange(date)
Registry->>Modules: onDateChange(date)
View->>Manager: onunload()
Manager->>Registry: destroyAll()
Registry->>Modules: destroy()
Manager->>Resizer: destroy()
Manager->>Store: destroy() (flush pending saves)
State Persistence & Debouncing Model¶
Changes to the sidebar layout (resizing width or folding accordion cards) occur frequently during active user workflows. To avoid saturating vault file I/O:
- In-Memory Mutex: The
SidebarStateStoreimmediately updatesPluginState.getSettings().sidebarConfigin-memory. - Debounced Disk Sync: It triggers
PluginState.saveSettings(false), which internally batches disk persistence via an asynchronous debounce timer. - Flush on Unload: When
CalendarViewunmounts,SidebarStateStore.destroy()invokesPluginState.saveSettings(true)with immediate flushing to guarantee zero state loss.
Lazy-Loading & Jest Test Isolation¶
To preserve sub-millisecond plugin startup and isolate test environments from heavy ESM bundles:
- The
SidebarModuleRegistrysupports asynchronous module instantiation (SidebarModuleFactory = () => ISidebarModule | Promise<ISidebarModule>). - Modules that rely on complex external dependencies (such as
@fullcalendar/interactionDraggableviaTaskBacklogModule) are dynamically imported:this.registerModuleFactory('task-backlog', async () => { const { TaskBacklogModule } = await import('./TaskBacklogModule'); return new TaskBacklogModule(this.store); }); - This ensures that importing
CalendarViewin headless unit tests (e.g.FullCalendarAPI.test.ts) does not prematurely pull unmocked ESM libraries into CommonJS execution contexts.
Responsive & Reflow Engineering¶
The split-pane layout uses native CSS Flexbox with custom properties:
.ofc-calendar-shell {
display: flex;
flex-direction: row;
--sidebar-width: 290px;
}
.ofc-calendar-sidebar {
width: var(--sidebar-width, 290px);
min-width: 200px;
max-width: 520px;
}
.ofc-calendar-shell .ofc-calendar-container {
flex: 1 1 0;
min-width: 0;
}
- Dynamic Reflow: As the user drags the splitter,
SidebarResizerupdates--sidebar-widthon the container element in real-time. On pointer release, it executescontext.triggerResize(), invokingfullCalendarView.updateSize(). - Zero Mobile Interference: Mobile clients (
.is-phone) completely omit.ofc-calendar-sidebarand.ofc-calendar-sidebar-resizerviadisplay: none, giving the calendar canvas 100% viewport width without runtime overhead.