Major refactorering to get a hold on all these events
This commit is contained in:
parent
2a766cf685
commit
59b3c64c55
18 changed files with 1901 additions and 357 deletions
237
docs/date-mode-initialization-sequence.md
Normal file
237
docs/date-mode-initialization-sequence.md
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
# Calendar Plantempus - Date Mode Initialization Sequence
|
||||
|
||||
## Overview
|
||||
This document shows the complete initialization sequence and event flow for Date Mode in Calendar Plantempus, including when data is loaded and ready for rendering.
|
||||
|
||||
## Sequence Diagram
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Browser as Browser
|
||||
participant Index as index.ts
|
||||
participant Config as CalendarConfig
|
||||
participant Factory as CalendarTypeFactory
|
||||
participant CM as CalendarManager
|
||||
participant EM as EventManager
|
||||
participant GM as GridManager
|
||||
participant NM as NavigationManager
|
||||
participant VM as ViewManager
|
||||
participant ER as EventRenderer
|
||||
participant SM as ScrollManager
|
||||
participant EB as EventBus
|
||||
participant DOM as DOM
|
||||
|
||||
Note over Browser: Page loads calendar application
|
||||
Browser->>Index: Load application
|
||||
|
||||
Note over Index: PHASE 0: Pre-initialization Setup
|
||||
Index->>Config: new CalendarConfig()
|
||||
Config->>Config: loadCalendarType() - Read URL ?type=date
|
||||
Config->>Config: loadFromDOM() - Read data attributes
|
||||
Config->>Config: Set mode='date', period='week'
|
||||
|
||||
Index->>Factory: CalendarTypeFactory.initialize()
|
||||
Factory->>Factory: Create DateHeaderRenderer
|
||||
Factory->>Factory: Create DateColumnRenderer
|
||||
Factory->>Factory: Create DateEventRenderer
|
||||
Note over Factory: Strategy Pattern renderers ready
|
||||
|
||||
Note over Index: PHASE 1: Core Managers Construction
|
||||
Index->>CM: new CalendarManager(eventBus, config)
|
||||
CM->>EB: Subscribe to VIEW_CHANGE_REQUESTED
|
||||
CM->>EB: Subscribe to NAV_PREV, NAV_NEXT
|
||||
|
||||
Index->>NM: new NavigationManager(eventBus)
|
||||
NM->>EB: Subscribe to CALENDAR_INITIALIZED
|
||||
Note over NM: Will wait to call updateWeekInfo()
|
||||
|
||||
Index->>VM: new ViewManager(eventBus)
|
||||
VM->>EB: Subscribe to CALENDAR_INITIALIZED
|
||||
|
||||
Note over Index: PHASE 2: Data & Rendering Managers
|
||||
Index->>EM: new EventManager(eventBus)
|
||||
EM->>EB: Subscribe to CALENDAR_INITIALIZED
|
||||
Note over EM: Will wait to load data
|
||||
|
||||
Index->>ER: new EventRenderer(eventBus)
|
||||
ER->>EB: Subscribe to EVENTS_LOADED
|
||||
ER->>EB: Subscribe to GRID_RENDERED
|
||||
Note over ER: Needs BOTH events before rendering
|
||||
|
||||
Note over Index: PHASE 3: Layout Managers (Order Critical!)
|
||||
Index->>SM: new ScrollManager()
|
||||
SM->>EB: Subscribe to GRID_RENDERED
|
||||
Note over SM: Must subscribe BEFORE GridManager renders
|
||||
|
||||
Index->>GM: new GridManager()
|
||||
GM->>EB: Subscribe to CALENDAR_INITIALIZED
|
||||
GM->>EB: Subscribe to CALENDAR_DATA_LOADED
|
||||
GM->>GM: Set currentWeek = getWeekStart(new Date())
|
||||
Note over GM: Ready to render, but waiting
|
||||
|
||||
Note over Index: PHASE 4: Coordinated Initialization
|
||||
Index->>CM: initialize()
|
||||
|
||||
CM->>EB: emit(CALENDAR_INITIALIZING)
|
||||
CM->>CM: setView('week'), setCurrentDate()
|
||||
CM->>EB: emit(CALENDAR_INITIALIZED) ⭐
|
||||
|
||||
Note over EB: 🚀 CALENDAR_INITIALIZED triggers all managers
|
||||
|
||||
par EventManager Data Loading
|
||||
EB->>EM: CALENDAR_INITIALIZED
|
||||
EM->>EM: loadMockData() for date mode
|
||||
EM->>EM: fetch('/src/data/mock-events.json')
|
||||
Note over EM: Loading date-specific mock data
|
||||
EM->>EM: Process events for current week
|
||||
EM->>EB: emit(CALENDAR_DATA_LOADED, {calendarType: 'date', data})
|
||||
EM->>EB: emit(EVENTS_LOADED, {events: [...])
|
||||
|
||||
and GridManager Initial Rendering
|
||||
EB->>GM: CALENDAR_INITIALIZED
|
||||
GM->>GM: render()
|
||||
GM->>GM: updateGridStyles() - Set --grid-columns: 7
|
||||
GM->>GM: createHeaderSpacer()
|
||||
GM->>GM: createTimeAxis(dayStartHour, dayEndHour)
|
||||
GM->>GM: createGridContainer()
|
||||
|
||||
Note over GM: Strategy Pattern - Date Mode Rendering
|
||||
GM->>Factory: getHeaderRenderer('date') → DateHeaderRenderer
|
||||
GM->>GM: renderCalendarHeader() - Create day headers
|
||||
GM->>DOM: Create 7 swp-day-column elements
|
||||
|
||||
GM->>Factory: getColumnRenderer('date') → DateColumnRenderer
|
||||
GM->>GM: renderColumnContainer() - Date columns
|
||||
GM->>EB: emit(GRID_RENDERED) ⭐
|
||||
|
||||
and NavigationManager UI
|
||||
EB->>NM: CALENDAR_INITIALIZED
|
||||
NM->>NM: updateWeekInfo()
|
||||
NM->>DOM: Update week display in navigation
|
||||
NM->>EB: emit(WEEK_INFO_UPDATED)
|
||||
|
||||
and ViewManager Setup
|
||||
EB->>VM: CALENDAR_INITIALIZED
|
||||
VM->>VM: initializeView()
|
||||
VM->>EB: emit(VIEW_RENDERED)
|
||||
end
|
||||
|
||||
Note over GM: GridManager receives its own data event
|
||||
EB->>GM: CALENDAR_DATA_LOADED
|
||||
GM->>GM: updateGridStyles() - Recalculate columns if needed
|
||||
Note over GM: Grid already rendered, just update styles
|
||||
|
||||
Note over ER: 🎯 Critical Synchronization Point
|
||||
EB->>ER: EVENTS_LOADED
|
||||
ER->>ER: pendingEvents = events (store, don't render yet)
|
||||
Note over ER: Waiting for grid to be ready...
|
||||
|
||||
EB->>ER: GRID_RENDERED
|
||||
ER->>DOM: querySelectorAll('swp-day-column') - Check if ready
|
||||
DOM-->>ER: Return 7 day columns (ready!)
|
||||
|
||||
Note over ER: Both events loaded AND grid ready → Render!
|
||||
ER->>Factory: getEventRenderer('date') → DateEventRenderer
|
||||
ER->>ER: renderEvents(pendingEvents) using DateEventRenderer
|
||||
ER->>DOM: Position events in day columns
|
||||
ER->>ER: Clear pendingEvents
|
||||
ER->>EB: emit(EVENT_RENDERED)
|
||||
|
||||
Note over SM: ScrollManager sets up after grid is complete
|
||||
EB->>SM: GRID_RENDERED
|
||||
SM->>DOM: querySelector('swp-scrollable-content')
|
||||
SM->>SM: setupScrolling()
|
||||
SM->>SM: applyScrollbarStyling()
|
||||
SM->>SM: setupScrollSynchronization()
|
||||
|
||||
Note over Index: 🎊 Date Mode Initialization Complete!
|
||||
Note over Index: Ready for user interaction
|
||||
```
|
||||
|
||||
## Key Initialization Phases
|
||||
|
||||
### Phase 0: Pre-initialization Setup
|
||||
- **CalendarConfig**: Loads URL parameters (`?type=date`) and DOM attributes
|
||||
- **CalendarTypeFactory**: Creates strategy pattern renderers for date mode
|
||||
|
||||
### Phase 1: Core Managers Construction
|
||||
- **CalendarManager**: Central coordinator
|
||||
- **NavigationManager**: Week navigation controls
|
||||
- **ViewManager**: View state management
|
||||
|
||||
### Phase 2: Data & Rendering Managers
|
||||
- **EventManager**: Handles data loading
|
||||
- **EventRenderer**: Manages event display with synchronization
|
||||
|
||||
### Phase 3: Layout Managers (Order Critical!)
|
||||
- **ScrollManager**: Must subscribe before GridManager renders
|
||||
- **GridManager**: Main grid rendering
|
||||
|
||||
### Phase 4: Coordinated Initialization
|
||||
- **CalendarManager.initialize()**: Triggers `CALENDAR_INITIALIZED` event
|
||||
- All managers respond simultaneously but safely
|
||||
|
||||
## Critical Synchronization Points
|
||||
|
||||
### 1. Event-Grid Synchronization
|
||||
```typescript
|
||||
// EventRenderer waits for BOTH events
|
||||
if (this.pendingEvents.length > 0) {
|
||||
const columns = document.querySelectorAll('swp-day-column'); // DATE MODE
|
||||
if (columns.length > 0) { // Grid must exist first
|
||||
this.renderEvents(this.pendingEvents);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Scroll-Grid Dependency
|
||||
```typescript
|
||||
// ScrollManager only sets up after grid is rendered
|
||||
eventBus.on(EventTypes.GRID_RENDERED, () => {
|
||||
this.setupScrolling(); // Safe to access DOM now
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Manager Construction Order
|
||||
```typescript
|
||||
// Critical order: ScrollManager subscribes BEFORE GridManager renders
|
||||
const scrollManager = new ScrollManager();
|
||||
const gridManager = new GridManager();
|
||||
```
|
||||
|
||||
## Date Mode Specifics
|
||||
|
||||
### Data Loading
|
||||
- Uses `/src/data/mock-events.json`
|
||||
- Processes events for current week
|
||||
- Emits `CALENDAR_DATA_LOADED` with `calendarType: 'date'`
|
||||
|
||||
### Grid Rendering
|
||||
- Creates 7 `swp-day-column` elements (weekDays: 7)
|
||||
- Uses `DateHeaderRenderer` strategy
|
||||
- Uses `DateColumnRenderer` strategy
|
||||
- Sets `--grid-columns: 7` CSS variable
|
||||
|
||||
### Event Rendering
|
||||
- Uses `DateEventRenderer` strategy
|
||||
- Positions events in day columns based on start/end time
|
||||
- Calculates pixel positions using `PositionUtils`
|
||||
|
||||
## Race Condition Prevention
|
||||
|
||||
1. **Subscription Before Action**: All managers subscribe during construction, act on `CALENDAR_INITIALIZED`
|
||||
2. **DOM Existence Checks**: Managers verify DOM elements exist before manipulation
|
||||
3. **Event Ordering**: `GRID_RENDERED` always fires before event rendering attempts
|
||||
4. **Pending States**: EventRenderer stores pending events until grid is ready
|
||||
5. **Coordinated Start**: Single `CALENDAR_INITIALIZED` event starts all processes
|
||||
|
||||
## Debugging Points
|
||||
|
||||
Key events to monitor during initialization:
|
||||
- `CALENDAR_INITIALIZED` - Start of coordinated setup
|
||||
- `CALENDAR_DATA_LOADED` - Date data ready
|
||||
- `GRID_RENDERED` - Grid structure complete
|
||||
- `EVENTS_LOADED` - Event data ready
|
||||
- `EVENT_RENDERED` - Events positioned in grid
|
||||
|
||||
This sequence ensures deterministic, race-condition-free initialization with comprehensive logging for debugging.
|
||||
270
docs/improved-initialization-strategy.md
Normal file
270
docs/improved-initialization-strategy.md
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
# Improved Calendar Initialization Strategy
|
||||
|
||||
## Current Problems
|
||||
|
||||
1. **Race Conditions**: Managers try DOM operations before DOM is ready
|
||||
2. **Sequential Blocking**: All initialization happens sequentially
|
||||
3. **Poor Error Handling**: No timeouts or retry mechanisms
|
||||
4. **Late Data Loading**: Data only loads after all managers are created
|
||||
|
||||
## Recommended New Architecture
|
||||
|
||||
### Phase 1: Early Parallel Startup
|
||||
```typescript
|
||||
// index.ts - Improved initialization
|
||||
export class CalendarInitializer {
|
||||
async initialize(): Promise<void> {
|
||||
console.log('📋 Starting Calendar initialization...');
|
||||
|
||||
// PHASE 1: Early parallel setup
|
||||
const setupPromises = [
|
||||
this.initializeConfig(), // Load URL params, DOM attrs
|
||||
this.initializeFactory(), // Setup strategy patterns
|
||||
this.preloadCalendarData(), // Start data loading early
|
||||
this.waitForDOMReady() // Ensure basic DOM exists
|
||||
];
|
||||
|
||||
await Promise.all(setupPromises);
|
||||
console.log('✅ Phase 1 complete: Config, Factory, Data preloading started');
|
||||
|
||||
// PHASE 2: Manager creation with dependencies
|
||||
await this.createManagersWithDependencies();
|
||||
|
||||
// PHASE 3: Coordinated activation
|
||||
await this.activateAllManagers();
|
||||
|
||||
console.log('🎊 Calendar fully initialized!');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 2: Dependency-Aware Manager Creation
|
||||
```typescript
|
||||
private async createManagersWithDependencies(): Promise<void> {
|
||||
const managers = new Map<string, any>();
|
||||
|
||||
// Core managers (no DOM dependencies)
|
||||
managers.set('config', calendarConfig);
|
||||
managers.set('eventBus', eventBus);
|
||||
managers.set('calendarManager', new CalendarManager(eventBus, calendarConfig));
|
||||
|
||||
// DOM-dependent managers (wait for DOM readiness)
|
||||
await this.waitForRequiredDOM(['swp-calendar', 'swp-calendar-nav']);
|
||||
|
||||
managers.set('navigationManager', new NavigationManager(eventBus));
|
||||
managers.set('viewManager', new ViewManager(eventBus));
|
||||
|
||||
// Data managers (can work with preloaded data)
|
||||
managers.set('eventManager', new EventManager(eventBus));
|
||||
managers.set('dataManager', new DataManager());
|
||||
|
||||
// Layout managers (need DOM structure + other managers)
|
||||
await this.waitForRequiredDOM(['swp-calendar-container']);
|
||||
|
||||
// CRITICAL ORDER: ScrollManager subscribes before GridManager renders
|
||||
managers.set('scrollManager', new ScrollManager());
|
||||
managers.set('gridManager', new GridManager());
|
||||
|
||||
// Rendering managers (need grid structure)
|
||||
managers.set('eventRenderer', new EventRenderer(eventBus));
|
||||
|
||||
this.managers = managers;
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Coordinated Activation
|
||||
```typescript
|
||||
private async activateAllManagers(): Promise<void> {
|
||||
// All managers created and subscribed, now activate in coordinated fashion
|
||||
const calendarManager = this.managers.get('calendarManager');
|
||||
|
||||
// This triggers CALENDAR_INITIALIZED, but now all managers are ready
|
||||
await calendarManager.initialize();
|
||||
|
||||
// Wait for critical initialization events
|
||||
await Promise.all([
|
||||
this.waitForEvent('CALENDAR_DATA_LOADED', 10000),
|
||||
this.waitForEvent('GRID_RENDERED', 5000),
|
||||
this.waitForEvent('EVENTS_LOADED', 10000)
|
||||
]);
|
||||
|
||||
// Ensure event rendering completes
|
||||
await this.waitForEvent('EVENT_RENDERED', 3000);
|
||||
}
|
||||
```
|
||||
|
||||
## Specific Timing Improvements
|
||||
|
||||
### 1. Early Data Preloading
|
||||
```typescript
|
||||
private async preloadCalendarData(): Promise<void> {
|
||||
const currentDate = new Date();
|
||||
const mode = calendarConfig.getCalendarMode();
|
||||
|
||||
// Start loading data for current period immediately
|
||||
const dataManager = new DataManager();
|
||||
const currentPeriod = this.getCurrentPeriod(currentDate, mode);
|
||||
|
||||
// Don't await - let this run in background
|
||||
const dataPromise = dataManager.fetchEventsForPeriod(currentPeriod);
|
||||
|
||||
// Also preload adjacent periods
|
||||
const prevPeriod = this.getPreviousPeriod(currentDate, mode);
|
||||
const nextPeriod = this.getNextPeriod(currentDate, mode);
|
||||
|
||||
// Store promises for later use
|
||||
this.preloadPromises = {
|
||||
current: dataPromise,
|
||||
previous: dataManager.fetchEventsForPeriod(prevPeriod),
|
||||
next: dataManager.fetchEventsForPeriod(nextPeriod)
|
||||
};
|
||||
|
||||
console.log('📊 Data preloading started for current, previous, and next periods');
|
||||
}
|
||||
```
|
||||
|
||||
### 2. DOM Readiness Verification
|
||||
```typescript
|
||||
private async waitForRequiredDOM(selectors: string[]): Promise<void> {
|
||||
const maxWait = 5000; // 5 seconds max
|
||||
const checkInterval = 100; // Check every 100ms
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
while (Date.now() - startTime < maxWait) {
|
||||
const missing = selectors.filter(selector => !document.querySelector(selector));
|
||||
|
||||
if (missing.length === 0) {
|
||||
console.log(`✅ Required DOM elements found: ${selectors.join(', ')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, checkInterval));
|
||||
}
|
||||
|
||||
throw new Error(`❌ Timeout waiting for DOM elements: ${selectors.join(', ')}`);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Manager Base Class with Proper Lifecycle
|
||||
```typescript
|
||||
export abstract class BaseManager {
|
||||
protected isInitialized = false;
|
||||
protected requiredDOMSelectors: string[] = [];
|
||||
|
||||
constructor() {
|
||||
// Don't call init() immediately in constructor!
|
||||
console.log(`${this.constructor.name}: Created but not initialized`);
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
if (this.isInitialized) {
|
||||
console.log(`${this.constructor.name}: Already initialized, skipping`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for required DOM elements
|
||||
if (this.requiredDOMSelectors.length > 0) {
|
||||
await this.waitForDOM(this.requiredDOMSelectors);
|
||||
}
|
||||
|
||||
// Perform manager-specific initialization
|
||||
await this.performInitialization();
|
||||
|
||||
this.isInitialized = true;
|
||||
console.log(`${this.constructor.name}: Initialization complete`);
|
||||
}
|
||||
|
||||
protected abstract performInitialization(): Promise<void>;
|
||||
|
||||
private async waitForDOM(selectors: string[]): Promise<void> {
|
||||
// Same DOM waiting logic as above
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Enhanced GridManager
|
||||
```typescript
|
||||
export class GridManager extends BaseManager {
|
||||
protected requiredDOMSelectors = ['swp-calendar-container'];
|
||||
|
||||
constructor() {
|
||||
super(); // Don't call this.init()!
|
||||
this.currentWeek = this.getWeekStart(new Date());
|
||||
}
|
||||
|
||||
protected async performInitialization(): Promise<void> {
|
||||
// Now safe to find elements - DOM guaranteed to exist
|
||||
this.findElements();
|
||||
this.subscribeToEvents();
|
||||
|
||||
// Wait for CALENDAR_INITIALIZED before rendering
|
||||
await this.waitForEvent('CALENDAR_INITIALIZED');
|
||||
|
||||
console.log('GridManager: Starting initial render');
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Enhanced EventRenderer with Better Synchronization
|
||||
```typescript
|
||||
export class EventRenderer extends BaseManager {
|
||||
private dataReady = false;
|
||||
private gridReady = false;
|
||||
private pendingEvents: CalendarEvent[] = [];
|
||||
|
||||
protected async performInitialization(): Promise<void> {
|
||||
this.subscribeToEvents();
|
||||
|
||||
// Wait for both data and grid in parallel
|
||||
const [eventsData] = await Promise.all([
|
||||
this.waitForEvent('EVENTS_LOADED'),
|
||||
this.waitForEvent('GRID_RENDERED')
|
||||
]);
|
||||
|
||||
console.log('EventRenderer: Both events and grid ready, rendering now');
|
||||
this.renderEvents(eventsData.events);
|
||||
}
|
||||
|
||||
private subscribeToEvents(): void {
|
||||
this.eventBus.on(EventTypes.EVENTS_LOADED, (e: Event) => {
|
||||
const detail = (e as CustomEvent).detail;
|
||||
this.pendingEvents = detail.events;
|
||||
this.dataReady = true;
|
||||
this.tryRender();
|
||||
});
|
||||
|
||||
this.eventBus.on(EventTypes.GRID_RENDERED, () => {
|
||||
this.gridReady = true;
|
||||
this.tryRender();
|
||||
});
|
||||
}
|
||||
|
||||
private tryRender(): void {
|
||||
if (this.dataReady && this.gridReady && this.pendingEvents.length > 0) {
|
||||
this.renderEvents(this.pendingEvents);
|
||||
this.pendingEvents = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Benefits of New Architecture
|
||||
|
||||
1. **🚀 Parallel Operations**: Data loading starts immediately while managers are being created
|
||||
2. **🛡️ Race Condition Prevention**: DOM readiness verified before operations
|
||||
3. **⚡ Better Performance**: Critical path optimized, non-critical operations parallelized
|
||||
4. **🔧 Better Error Handling**: Timeouts and retry mechanisms
|
||||
5. **📊 Predictable Timing**: Clear phases with guaranteed completion order
|
||||
6. **🐛 Easier Debugging**: Clear lifecycle events and logging
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
1. **Phase 1**: Create BaseManager class and update existing managers
|
||||
2. **Phase 2**: Implement CalendarInitializer with parallel setup
|
||||
3. **Phase 3**: Add DOM readiness verification throughout
|
||||
4. **Phase 4**: Implement data preloading strategy
|
||||
5. **Phase 5**: Add comprehensive error handling and timeouts
|
||||
|
||||
This architecture ensures reliable, fast, and maintainable calendar initialization.
|
||||
Loading…
Add table
Add a link
Reference in a new issue