Calendar/docs/improved-initialization-strategy.md

9.1 KiB

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

Phase 1: Early Parallel Startup

// 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

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

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

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

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

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

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

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.