Simplifies workweek change event propagation across managers Removes redundant grid rerendering logic and focuses on clean event communication Prepares infrastructure for more flexible workweek configuration updates Relates to debug-gridstyle branch
8.2 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Calendar Plantempus is a professional TypeScript calendar component with offline-first architecture, drag-and-drop functionality, and real-time synchronization capabilities.
Build & Development Commands
# Build the project (bundles to wwwroot/js/calendar.js)
npm run build
# Watch mode for development
npm run watch
# Clean build output
npm run clean
# Type check only
npx tsc --noEmit
# Run all tests
npm test
# Run tests in watch mode
npm run test
# Run tests once and exit
npm run test:run
# Run tests with UI
npm run test:ui
# CSS Development
npm run css:build # Build CSS
npm run css:watch # Watch and rebuild CSS
npm run css:build:prod # Build minified production CSS
npm run css:analyze # Analyze CSS metrics
Architecture
Core Design Pattern: Dependency Injection with NovaDI
The application uses NovaDI (@novadi/core) for dependency injection. All managers, services, and repositories are registered in src/index.ts and resolved through the DI container.
Key principle: Never instantiate managers or services directly with new. Always use constructor injection and register types in the container.
Event-Driven Architecture
The application uses a centralized EventBus (src/core/EventBus.ts) built on DOM CustomEvents for all inter-component communication. This is the ONLY way components should communicate.
- All event types are defined in
src/constants/CoreEvents.ts(reduced from 102+ to ~20 core events) - Components emit events via
eventBus.emit(CoreEvents.EVENT_NAME, payload) - Components subscribe via
eventBus.on(CoreEvents.EVENT_NAME, handler) - Never call methods directly between managers - always use events
Manager Hierarchy
CalendarManager (src/managers/CalendarManager.ts) - Top-level coordinator
- Manages calendar state (current view, current date)
- Orchestrates initialization sequence
- Coordinates other managers via EventBus
Key Managers:
- EventManager - Event CRUD operations, data loading from repository
- GridManager - Renders time grid structure
- ViewManager - Handles view switching (day/week/month)
- NavigationManager - Date navigation and period calculations
- DragDropManager - Advanced drag-and-drop with smooth animations, type conversion (timed ↔ all-day), scroll compensation
- ResizeHandleManager - Event resizing with visual feedback
- AllDayManager - All-day event layout and rendering
- HeaderManager - Date headers and all-day event container
- ScrollManager - Scroll behavior and position management
- EdgeScrollManager - Automatic scrolling at viewport edges during drag
Repository Pattern
Event data access is abstracted through the IEventRepository interface (src/repositories/IEventRepository.ts):
- IndexedDBEventRepository - Primary: Local storage with offline support
- ApiEventRepository - Sends changes to backend API
- MockEventRepository - Legacy: Loads from JSON file
All repository methods accept an UpdateSource parameter ('local' | 'remote') to distinguish user actions from remote updates.
Offline-First Sync Architecture
SyncManager (src/workers/SyncManager.ts) provides background synchronization:
- Local changes are written to IndexedDB immediately
- Operations are queued in OperationQueue
- SyncManager processes queue when online (5-second polling)
- Failed operations retry with exponential backoff (max 5 retries)
- Events have
syncStatus: 'synced' | 'pending' | 'error'
Rendering Strategy Pattern
EventRenderingService (src/renderers/EventRendererManager.ts) uses strategy pattern:
- IEventRenderer interface defines rendering contract
- DateEventRenderer - Renders timed events in day columns
- AllDayEventRenderer - Renders all-day events in header
- Strategies can be swapped without changing core logic
Layout Engines
EventStackManager (src/managers/EventStackManager.ts) - Uses CSS flexbox for overlapping events:
- Groups overlapping events into stacks
- Calculates flex positioning (basis, grow, shrink)
- Handles multi-column spanning events
AllDayLayoutEngine (src/utils/AllDayLayoutEngine.ts) - Row-based layout for all-day events:
- Detects overlaps and assigns row positions
- Supports collapsed view (max 4 rows) with "+N more" indicator
- Calculates container height dynamically
Configuration System
Configuration is loaded from wwwroot/data/calendar-config.json via ConfigManager:
- GridSettings - Hour height, work hours, snap interval
- DateViewSettings - Period type, first day of week
- TimeFormatConfig - Timezone, locale, 12/24-hour format
- WorkWeekSettings - Configurable work week presets
- Interaction - Enable/disable drag, resize, create
Access via injected Configuration instance, never load config directly.
Important Patterns & Conventions
Event Type Conversion (Drag & Drop)
When dragging events between timed grid and all-day area:
- Timed → All-day:
DragDropManageremitsdrag:mouseenter-header,AllDayManagercreates all-day clone - All-day → Timed:
DragDropManageremitsdrag:mouseenter-column,EventRenderingServicecreates timed clone - Original element is marked with
data-conversion-source="true" - Clone is marked with
data-converted-clone="true"
Scroll Compensation During Drag
DragDropManager tracks scroll delta during edge-scrolling:
- Listens to
edge-scroll:scrollingevents - Accumulates
scrollDeltaYfrom scroll events - Compensates dragged element position:
targetY = mouseY - scrollDeltaY - mouseOffset.y - Prevents visual "jumping" during scroll
Grid Snapping
When dropping events, snap to time grid:
- Get mouse Y position relative to column
- Convert to time using
PositionUtils.getTimeAtPosition() - Account for
mouseOffset.y(click position within event) - Snap to nearest
snapInterval(default 15 minutes)
Testing with Vitest
Tests use Vitest with jsdom environment:
- Setup file:
test/setup.ts - Test helpers:
test/helpers/dom-helpers.ts - Run single test:
npm test -- <test-file-name>
Key Files to Know
src/index.ts- DI container setup and initializationsrc/core/EventBus.ts- Central event dispatchersrc/constants/CoreEvents.ts- All event type constantssrc/types/CalendarTypes.ts- Core type definitionssrc/managers/CalendarManager.ts- Main coordinatorsrc/managers/DragDropManager.ts- Detailed drag-drop architecture docssrc/configurations/CalendarConfig.ts- Configuration schemawwwroot/data/calendar-config.json- Runtime configuration
Common Tasks
Adding a New Event Type to CoreEvents
- Add constant to
src/constants/CoreEvents.ts - Define payload type in
src/types/EventTypes.ts - Emit with
eventBus.emit(CoreEvents.NEW_EVENT, payload) - Subscribe with
eventBus.on(CoreEvents.NEW_EVENT, handler)
Adding a New Manager
- Create in
src/managers/ - Inject dependencies via constructor (EventBus, Configuration, other managers)
- Register in DI container in
src/index.ts:builder.registerType(NewManager).as<NewManager>() - Communicate via EventBus only, never direct method calls
- Initialize in CalendarManager if needed
Modifying Event Data
Always go through EventManager:
- Create:
eventManager.createEvent(eventData) - Update:
eventManager.updateEvent(id, updates) - Delete:
eventManager.deleteEvent(id)
EventManager handles repository calls, event emission, and UI updates.
Debugging
Debug mode is enabled in development:
eventBus.setDebug(true); // In src/index.ts
Access debug interface in browser console:
window.calendarDebug.eventBus.getEventLog()
window.calendarDebug.calendarManager
window.calendarDebug.eventManager
Dependencies
- @novadi/core - Dependency injection framework
- date-fns / date-fns-tz - Date manipulation and timezone support
- fuse.js - Fuzzy search for event filtering
- esbuild - Fast bundler for development
- vitest - Testing framework
- postcss - CSS processing and optimization