# DataSource Architecture Implementation

**Date:** November 13, 2025
**Type:** Architecture refactoring, Data flow optimization
**Status:** ✅ Complete (with course corrections)
**Main Goal:** Implement DataSource pattern to centralize date calculation and prepare for dual-mode calendar (date-based vs resource-based)

---

## Executive Summary

Successfully implemented DataSource architecture to centralize date calculations and clean up rendering responsibilities. Initial implementation had architectural violations that were corrected through user guidance.

**Key Outcomes:**
- ✅ DateColumnDataSource created as single source of truth for dates
- ✅ GridRenderer responsibility corrected (structure only, not event rendering)
- ✅ ColumnRenderer receives dates instead of calculating them
- ✅ Navigation flow fixed (no duplicate rendering)
- ✅ Clean separation of concerns restored
- ⚠️ Initial conversation compaction caused architectural mistakes

**Code Volume:**
- Created: 2 new files (DateColumnDataSource.ts, ColumnDataSource.ts interface)
- Modified: 5 files (GridManager.ts, NavigationManager.ts, GridRenderer.ts, ColumnRenderer.ts, EventTypes.ts)

---

## User Corrections & Course Changes

### Correction #1: GridRenderer Event Rendering Violation

**What Happened:**
After conversation compaction, I mistakenly added event rendering logic directly into GridRenderer.

**Code I Added (WRONG):**
```typescript
// GridRenderer.ts - renderColumnContainer()
private renderColumnContainer(dates: Date[], events: ICalendarEvent[]): void {
  dates.forEach(date => {
    const eventsForDate = events.filter(event => {...});

    // Created columns...

    // ❌ WRONG: Rendering events directly in GridRenderer
    this.renderColumnEvents(eventsForDate, eventsLayer);
  });
}

// Added entire event layout logic to GridRenderer
private renderColumnEvents() { ... }
private renderGridGroup() { ... }
private renderEvent() { ... }
```

**User Intervention:**
```
"hvorfor har du flyttet eventrendering iind i griidrendere?
det er jo columnsrenderes opgave"
```

followed by:

```
"ja naturligivs da"
```

**Problem:**
- GridRenderer should ONLY create grid structure (skeleton)
- Event rendering is EventRenderingService's responsibility
- ColumnRenderer creates columns, not GridRenderer's loop
- Violated separation of concerns
- Duplicated logic that already existed in DateEventRenderer

**Correct Solution:**
```typescript
// GridRenderer.ts - renderColumnContainer()
private renderColumnContainer(dates: Date[], events: ICalendarEvent[]): void {
  // Delegate to ColumnRenderer
  this.columnRenderer.render(columnContainer, {
    dates: dates,
    config: this.config
  });
  // Events rendered by EventRenderingService via GRID_RENDERED event
}
```

**Lesson:** After conversation compaction, I lost context about architectural boundaries. GridRenderer = structure, EventRenderingService = events, ColumnRenderer = columns. Never mix responsibilities.

---

### Correction #2: NavigationManager Configuration Injection

**What Happened:**
I attempted to inject Configuration into NavigationManager so it could calculate dates.

**User Intervention:**
```
"hvad er det her du har lavet... NavigationManager skal bruge Configuration
for at få adgang til workweek settings, så den kan beregne dates.
Lad mig opdatere det:

ja det vi nok. Men hva dbruger GridManager det event tiil?"
```

**Problem:**
- NavigationManager doesn't need to know about workweek settings
- Date calculation should be DataSource's responsibility
- NavigationManager should focus on animation, not data

**Initial Wrong Approach:**
```typescript
// NavigationManager.ts
constructor(..., config: Configuration) {
  this.config = config;
}

private animateTransition() {
  const workWeekSettings = this.config.getWorkWeekSettings();
  const dates = this.dateService.getWorkWeekDates(targetWeek, workWeekSettings.workDays);
  // ❌ NavigationManager calculating dates manually
}
```

**Correct Solution:**
```typescript
// NavigationManager.ts
constructor(..., config: Configuration) {
  this.config = config;
  this.dataSource = new DateColumnDataSource(dateService, config, this.currentWeek, 'week');
}

private animateTransition() {
  this.dataSource.setCurrentDate(targetWeek);
  const dates = this.dataSource.getColumns();
  // ✅ DataSource calculates dates
}
```

**Lesson:** NavigationManager needed Configuration, but not to calculate dates itself. It needed it to create a DataSource instance, which handles date calculation.

---

### Correction #3: GridManager Navigation Rendering Conflict

**What Happened:**
GridManager was calling `render()` when receiving NAVIGATION_COMPLETED event, creating conflict with NavigationManager's animation.

**User Analysis:**
I explained the problem flow:
```
1. NavigationManager creates NEW grid with animation
2. After animation: emits NAVIGATION_COMPLETED
3. GridManager listens and calls render() → updates OLD grid
4. Result: Two grids, broken state
```

User response:
```
"ja" (approved the fix)
```

**Problem:**
```typescript
// GridManager.ts - BEFORE (WRONG)
eventBus.on(CoreEvents.NAVIGATION_COMPLETED, (e: Event) => {
  this.currentDate = detail.newDate;
  this.dataSource.setCurrentDate(this.currentDate);
  this.render(); // ❌ Re-renders, conflicts with NavigationManager's new grid
});
```

**Correct Solution:**
```typescript
// GridManager.ts - AFTER (CORRECT)
// Listen for navigation events from NavigationManager
// NavigationManager has already created new grid with animation
// GridManager only needs to update state, NOT re-render
eventBus.on(CoreEvents.NAVIGATION_COMPLETED, (e: Event) => {
  this.currentDate = detail.newDate;
  this.dataSource.setCurrentDate(this.currentDate);
  // Do NOT call render() - NavigationManager already created new grid
});
```

**Lesson:** Two managers can't both render during navigation. NavigationManager owns animation rendering, GridManager only syncs state.

---

## Implementation Details

### Phase 1: DateColumnDataSource Creation

**File Created:** `src/datasources/DateColumnDataSource.ts`

**Purpose:** Single source of truth for date calculation based on workweek settings.

```typescript
export class DateColumnDataSource implements IColumnDataSource {
  private dateService: DateService;
  private config: Configuration;
  private currentDate: Date;
  private currentView: CalendarView;

  public getColumns(): Date[] {
    switch (this.currentView) {
      case 'week':
        return this.getWeekDates();
      case 'month':
        return this.getMonthDates();
      case 'day':
        return [this.currentDate];
    }
  }

  private getWeekDates(): Date[] {
    const weekStart = this.getISOWeekStart(this.currentDate);
    const workWeekSettings = this.config.getWorkWeekSettings();
    return this.dateService.getWorkWeekDates(weekStart, workWeekSettings.workDays);
  }
}
```

**Key Methods:**
- `getColumns()` - Returns Date[] based on current view
- `setCurrentDate(date)` - Updates current date
- `setCurrentView(view)` - Switches between day/week/month
- `getType()` - Returns 'date' (vs 'resource' for future mode)

---

### Phase 2: GridManager Integration

**File Modified:** `src/managers/GridManager.ts`

**Before:**
```typescript
// GridManager calculated dates itself
const weekStart = this.getISOWeekStart(this.currentDate);
const workWeekSettings = this.config.getWorkWeekSettings();
const dates = this.dateService.getWorkWeekDates(weekStart, workWeekSettings.workDays);
```

**After:**
```typescript
// GridManager uses DataSource
constructor(...) {
  this.dataSource = new DateColumnDataSource(dateService, config, this.currentDate, this.currentView);
}

public async render(): Promise<void> {
  const dates = this.dataSource.getColumns(); // Single source of truth
  const events = await this.eventManager.getEventsForPeriod(dates[0], dates[dates.length - 1]);

  this.gridRenderer.renderGrid(this.container, this.currentDate, this.currentView, dates, events);

  eventBus.emit(CoreEvents.GRID_RENDERED, {
    container: this.container,
    currentDate: this.currentDate,
    dates: dates
  });
}
```

**Navigation Handling Fixed:**
```typescript
eventBus.on(CoreEvents.NAVIGATION_COMPLETED, (e: Event) => {
  this.currentDate = detail.newDate;
  this.dataSource.setCurrentDate(this.currentDate);
  // Removed render() call - NavigationManager already created new grid
});
```

---

### Phase 3: NavigationManager DataSource Integration

**File Modified:** `src/managers/NavigationManager.ts`

**Added:**
```typescript
import { DateColumnDataSource } from '../datasources/DateColumnDataSource';
import { Configuration } from '../configurations/CalendarConfig';

constructor(..., config: Configuration) {
  this.config = config;
  this.dataSource = new DateColumnDataSource(dateService, config, this.currentWeek, 'week');
}
```

**Animation Flow Updated:**
```typescript
private animateTransition(direction: 'prev' | 'next', targetWeek: Date): void {
  // Update DataSource with target week and get dates
  this.dataSource.setCurrentDate(targetWeek);
  const dates = this.dataSource.getColumns();

  // Create new grid with correct dates
  newGrid = this.gridRenderer.createNavigationGrid(container, dates);

  // Animate transition...

  // After animation: emit NAVIGATION_COMPLETED
  this.eventBus.emit(CoreEvents.NAVIGATION_COMPLETED, {
    direction,
    newDate: this.currentWeek
  });
}
```

---

### Phase 4: GridRenderer Responsibility Cleanup

**File Modified:** `src/renderers/GridRenderer.ts`

**Removed:**
- WorkHoursManager injection (ColumnRenderer handles it)
- Event rendering logic (EventRenderingService handles it)
- Column creation loop (ColumnRenderer handles it)

**Kept:**
- Grid skeleton creation (swp-grid-container, swp-time-grid)
- Time axis rendering
- Delegation to ColumnRenderer

**Before (WRONG - after conversation compaction):**
```typescript
private renderColumnContainer(dates: Date[], events: ICalendarEvent[]): void {
  dates.forEach(date => {
    const column = document.createElement('swp-day-column');
    // Apply work hours styling
    this.applyWorkHoursStyling(column, date);
    // Render events
    this.renderColumnEvents(eventsForDate, eventsLayer);
  });
}
```

**After (CORRECT):**
```typescript
private renderColumnContainer(dates: Date[], events: ICalendarEvent[]): void {
  // Delegate to ColumnRenderer
  this.columnRenderer.render(columnContainer, {
    dates: dates,
    config: this.config
  });
  // Events rendered by EventRenderingService via GRID_RENDERED event
}
```

---

### Phase 5: ColumnRenderer Context Update

**File Modified:** `src/renderers/ColumnRenderer.ts`

**Context Interface Changed:**
```typescript
// Before
export interface IColumnRenderContext {
  currentWeek: Date;
  config: Configuration;
}

// After
export interface IColumnRenderContext {
  dates: Date[];  // ← Receives dates instead of calculating them
  config: Configuration;
}
```

**Render Method Updated:**
```typescript
// Before
render(columnContainer: HTMLElement, context: IColumnRenderContext): void {
  const { currentWeek, config } = context;
  const workWeekSettings = config.getWorkWeekSettings();
  const dates = this.dateService.getWorkWeekDates(currentWeek, workWeekSettings.workDays);
  // ❌ ColumnRenderer calculating dates
}

// After
render(columnContainer: HTMLElement, context: IColumnRenderContext): void {
  const { dates } = context;
  // ✅ ColumnRenderer receives dates from DataSource

  dates.forEach((date) => {
    const column = document.createElement('swp-day-column');
    this.applyWorkHoursToColumn(column, date);
    // ...
  });
}
```

---

## Architecture Flow

### Before DataSource:

```
┌──────────────────┐
│  GridManager     │ ← Calculates dates manually
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│  GridRenderer    │ ← No dates parameter
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│ ColumnRenderer   │ ← Calculates dates again (duplicate logic)
└──────────────────┘

NavigationManager: Also calculates dates separately
```

### After DataSource:

```
┌─────────────────────┐
│ DateColumnDataSource│ ← Single source of truth for dates
└──────────┬──────────┘
           │
      ┌────┴────────────────────┐
      │                         │
┌─────▼────────┐       ┌────────▼──────────┐
│ GridManager  │       │ NavigationManager │
└─────┬────────┘       └────────┬──────────┘
      │                         │
      │  dates[]                │  dates[]
      │                         │
      ▼                         ▼
┌──────────────────┐   ┌──────────────────┐
│  GridRenderer    │   │  GridRenderer    │
└────────┬─────────┘   │ (createNavGrid)  │
         │             └──────────────────┘
         ▼
┌──────────────────┐
│ ColumnRenderer   │ ← Receives dates, creates columns
└────────┬─────────┘
         │
         ▼
┌──────────────────┐
│ swp-day-column   │ ← Column structure with work hours
└──────────────────┘

EventRenderingService ← Listens to GRID_RENDERED, renders events
```

---

## Responsibilities After Refactoring

### DateColumnDataSource
- ✅ Calculate dates based on view (day/week/month)
- ✅ Apply workweek settings
- ✅ Single source of truth for dates

### GridManager
- ✅ Coordinate rendering flow
- ✅ Get dates from DataSource
- ✅ Fetch events from EventManager
- ✅ Call GridRenderer with dates + events
- ✅ Emit GRID_RENDERED event
- ❌ NOT calculate dates itself
- ❌ NOT render during NAVIGATION_COMPLETED

### NavigationManager
- ✅ Handle animation
- ✅ Use DataSource to get dates for new grid
- ✅ Create new grid via GridRenderer.createNavigationGrid()
- ✅ Emit NAVIGATION_COMPLETED after animation
- ❌ NOT calculate dates manually

### GridRenderer
- ✅ Create grid skeleton structure
- ✅ Render time axis
- ✅ Delegate column creation to ColumnRenderer
- ❌ NOT create columns directly
- ❌ NOT render events
- ❌ NOT handle work hours styling

### ColumnRenderer
- ✅ Receive dates from context
- ✅ Create swp-day-column elements
- ✅ Apply work hours styling
- ✅ Create eventsLayer structure
- ❌ NOT calculate dates itself

### EventRenderingService
- ✅ Listen to GRID_RENDERED event
- ✅ Render events into columns
- ✅ Handle event layout (stacked vs grid)
- ❌ NOT be called directly by GridRenderer

---

## Challenges & Solutions

### Challenge 1: Conversation Compaction Memory Loss

**Issue:** After conversation compaction, I lost context about architectural boundaries and started violating separation of concerns.

**Impact:**
- Added event rendering to GridRenderer
- Added column creation loop to GridRenderer
- Duplicated logic that already existed

**Solution:**
User corrections guided me back to correct architecture:
1. GridRenderer delegates to ColumnRenderer
2. EventRenderingService handles events via GRID_RENDERED
3. DataSource calculates dates, not managers

**Lesson:** After conversation compaction, explicitly review architectural boundaries before implementing. Ask about responsibilities if unsure.

---

### Challenge 2: Navigation Rendering Conflict

**Issue:** Both NavigationManager and GridManager tried to render during navigation, creating duplicate grids.

**Root Cause:**
```
NavigationManager: Creates NEW grid with animation
GridManager: Receives NAVIGATION_COMPLETED → renders AGAIN
Result: Two grids, broken state
```

**Solution:**
GridManager only updates state on NAVIGATION_COMPLETED, doesn't re-render:
```typescript
eventBus.on(CoreEvents.NAVIGATION_COMPLETED, (e: Event) => {
  this.currentDate = detail.newDate;
  this.dataSource.setCurrentDate(this.currentDate);
  // Removed render() call
});
```

---

### Challenge 3: DataSource Scope Confusion

**Issue:** Initially unsure whether NavigationManager should calculate dates or use DataSource.

**User Guidance:**
NavigationManager should use DateColumnDataSource just like GridManager, ensuring consistent date calculation.

**Solution:**
Both managers create their own DataSource instances:
```typescript
// GridManager
this.dataSource = new DateColumnDataSource(dateService, config, this.currentDate, this.currentView);

// NavigationManager
this.dataSource = new DateColumnDataSource(dateService, config, this.currentWeek, 'week');
```

---

## Navigation Flow (Final)

### User Clicks "Next Week":

```
1. NavigationButtons
   ├─ Calculates newDate (next week)
   └─ emit NAV_BUTTON_CLICKED { direction: 'next', newDate }

2. NavigationManager (listens to NAV_BUTTON_CLICKED)
   ├─ Updates DataSource with newDate
   ├─ Gets dates from DataSource.getColumns()
   ├─ Creates NEW grid: createNavigationGrid(dates)
   ├─ Animates slide transition (old grid out, new grid in)
   └─ After animation: emit NAVIGATION_COMPLETED { direction: 'next', newDate }

3. GridManager (listens to NAVIGATION_COMPLETED)
   ├─ Updates currentDate
   ├─ Updates dataSource.setCurrentDate()
   └─ Does NOT call render() ← Key fix!

4. EventRenderingService (listens to GRID_RENDERED - emitted during step 2)
   ├─ Reads GRID_RENDERED payload { container, dates }
   ├─ Fetches events for date range
   └─ Renders events into new grid's columns
```

**Result:** Single grid created by NavigationManager with animation, events rendered by EventRenderingService, GridManager stays in sync.

---

## Files Modified

### Created:
1. `src/types/ColumnDataSource.ts` - IColumnDataSource interface
2. `src/datasources/DateColumnDataSource.ts` - Date-based DataSource implementation

### Modified:
1. `src/managers/GridManager.ts`
   - Added DateColumnDataSource
   - Removed date calculation logic
   - Fixed NAVIGATION_COMPLETED handler (removed render() call)

2. `src/managers/NavigationManager.ts`
   - Added DateColumnDataSource
   - Uses DataSource.getColumns() instead of manual calculation
   - Added Configuration injection

3. `src/renderers/GridRenderer.ts`
   - Removed WorkHoursManager dependency
   - Removed event rendering logic (corrected after mistake)
   - Removed column creation loop (delegates to ColumnRenderer)
   - Updated createNavigationGrid() to accept dates array

4. `src/renderers/ColumnRenderer.ts`
   - Changed IColumnRenderContext to receive dates array
   - Removed date calculation logic
   - Simplified render() method

5. `src/types/EventTypes.ts`
   - No changes needed (considered adding skipRender flag but not required)

---

## Test Results

### Build Status:
```bash
npm run build
# ✅ Success
# Total transform time: 1105.12ms
# No TypeScript errors
```

### Manual Testing Required:
- ❓ Navigation animation works correctly
- ❓ Events render after navigation
- ❓ Work hours styling applied
- ❓ Workweek preset changes reflected

**Note:** No automated tests exist for navigation flow or DataSource.

---

## Lessons Learned

### 1. Conversation Compaction Awareness
- **Problem:** Lost architectural context after compaction
- **Impact:** Violated separation of concerns, added code to wrong places
- **Solution:** After compaction, explicitly review architectural boundaries before coding
- **Prevention:** Ask user to confirm responsibilities if unsure

### 2. Separation of Concerns Is Critical
- **GridRenderer:** Structure only
- **ColumnRenderer:** Columns only
- **EventRenderingService:** Events only
- **DataSource:** Dates only
- **Mixing these leads to maintenance hell**

### 3. Single Source of Truth Pattern
- **Before:** 3 places calculated dates (GridManager, ColumnRenderer, NavigationManager)
- **After:** 1 place calculates dates (DateColumnDataSource)
- **Benefit:** Change workweek logic once, applies everywhere

### 4. Event-Driven Rendering Conflicts
- Two managers can't both render on same event
- NavigationManager owns animation rendering
- GridManager only syncs state during navigation
- Clear ownership prevents bugs

### 5. Dependency Injection Must Match Responsibility
- GridRenderer doesn't need WorkHoursManager (ColumnRenderer does)
- NavigationManager needs Configuration (to create DataSource)
- Only inject what's actually used

---

## Future Considerations

### Potential Improvements

1. **Resource-Based View Support**
   - Create `ResourceColumnDataSource` implementing `IColumnDataSource`
   - Returns resource columns instead of date columns
   - Switch between DateColumnDataSource and ResourceColumnDataSource based on view mode

2. **DataSource Caching**
   - Cache calculated dates to avoid recalculation
   - Invalidate cache on workweek settings change
   - Performance optimization for frequent navigation

3. **Navigation Testing**
   - Add integration tests for navigation flow
   - Test animation + event rendering coordination
   - Verify no duplicate grids created

4. **DataSource Configuration**
   - Allow DataSource to be configured via DI container
   - Support custom DataSource implementations
   - Enable A/B testing of different date calculation strategies

---

## Code Statistics

### Lines Changed:
- Created: ~150 lines (DateColumnDataSource + interface)
- Modified: ~80 lines (GridManager, NavigationManager, GridRenderer, ColumnRenderer)
- Removed: ~60 lines (duplicate date calculations, event rendering in GridRenderer)
- Net: +170 lines

### Complexity:
- Cyclomatic complexity reduced (centralized date logic)
- Coupling reduced (fewer dependencies in GridRenderer)
- Cohesion improved (each class has single responsibility)

---

## Production Readiness

### ✅ Ready for Testing

**Confidence Level:** Medium-High

**Reasons:**
1. Build succeeds without errors
2. Architecture follows separation of concerns
3. All architectural violations corrected
4. User-guided course corrections applied

**Concerns:**
1. No automated tests for navigation flow
2. Manual testing required
3. Event rendering after navigation not verified
4. DataSource not tested with different workweek configs

**Testing Checklist:**
- [ ] Navigate next/previous week
- [ ] Verify events render after animation
- [ ] Check work hours styling applied
- [ ] Change workweek preset and navigate
- [ ] Verify no duplicate grids created
- [ ] Test navigation while events are being dragged (edge case)

**Rollback Plan:**
If navigation breaks:
1. Git revert to before DataSource implementation
2. Restore direct date calculations in managers
3. Restore GridManager render() call on NAVIGATION_COMPLETED

---

## Conclusion

Successfully implemented DataSource architecture despite initial architectural violations caused by conversation compaction. User corrections guided the implementation back to correct separation of concerns.

The migration process revealed:
1. Importance of maintaining architectural awareness after conversation compaction
2. Value of centralized data calculation (DataSource pattern)
3. Critical need for clear responsibility boundaries
4. How event-driven architecture requires careful coordination

**Key Success:** Single source of truth for dates, clean separation of structure/columns/events rendering, navigation rendering conflict resolved.

**Key Learning:** After conversation compaction, always verify architectural boundaries before implementing.

**Status:** ✅ Implementation complete, ready for manual testing.
