Calendar/CLAUDE.md
Janus C. H. Knudsen 0233e283e5 Adds customer list page with advanced search and filtering
Implements comprehensive customer management interface with:
- Dynamic table with sorting and search capabilities
- Responsive customer card panel with detailed profile information
- Interactive filtering and search functionality
- Modern, clean UI design with custom web components

Enhances user experience for customer data exploration and management
2025-12-22 13:14:56 +01:00

14 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. Supports both date-based (day/week/month) and resource-based (people, rooms) calendar views.

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 (watch mode)
npm test

# Run tests once and exit
npm run test:run

# Run tests with UI
npm run test:ui

# Run single test file
npm test -- <test-file-name>

# 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

Calendar Modes: Date vs Resource

The calendar supports two column modes, configured at initialization in src/index.ts:

Date Mode (DateColumnDataSource):

  • Columns represent dates (day/week/month views)
  • Uses DateHeaderRenderer and DateColumnRenderer

Resource Mode (ResourceColumnDataSource):

  • Columns represent resources (people, rooms, equipment)
  • Uses ResourceHeaderRenderer and ResourceColumnRenderer
  • Events filtered per-resource via IColumnInfo.events

Both modes implement IColumnDataSource interface, allowing polymorphic column handling.

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

Data access is abstracted through IApiRepository interface (src/repositories/IApiRepository.ts):

  • MockEventRepository, MockBookingRepository, MockCustomerRepository, MockResourceRepository - Development: Load from JSON files
  • ApiEventRepository, ApiBookingRepository, ApiCustomerRepository, ApiResourceRepository - Production: Backend API calls

All repository methods accept an UpdateSource parameter ('local' | 'remote') to distinguish user actions from remote updates.

Entity Service Pattern

IEntityService (src/storage/IEntityService.ts) provides polymorphic entity handling:

  • EventService, BookingService, CustomerService, ResourceService implement this interface
  • Encapsulates sync status manipulation (SyncManager delegates, never manipulates directly)
  • Uses entityType discriminator for runtime routing without switch statements
  • Enables polymorphic operations: Array<IEntityService<any>> works across all entity types

Offline-First Sync Architecture

SyncManager (src/workers/SyncManager.ts) provides background synchronization:

  1. Local changes are written to IndexedDB immediately
  2. Operations are queued in OperationQueue
  3. SyncManager processes queue when online (5-second polling)
  4. Failed operations retry with exponential backoff (max 5 retries)
  5. 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: DragDropManager emits drag:mouseenter-header, AllDayManager creates all-day clone
  • All-day → Timed: DragDropManager emits drag:mouseenter-column, EventRenderingService creates 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:

  1. Listens to edge-scroll:scrolling events
  2. Accumulates scrollDeltaY from scroll events
  3. Compensates dragged element position: targetY = mouseY - scrollDeltaY - mouseOffset.y
  4. Prevents visual "jumping" during scroll

Grid Snapping

When dropping events, snap to time grid:

  1. Get mouse Y position relative to column
  2. Convert to time using PositionUtils.getTimeAtPosition()
  3. Account for mouseOffset.y (click position within event)
  4. Snap to nearest snapInterval (default 15 minutes)

Testing with Vitest

Tests use Vitest with jsdom environment:

  • Config: vitest.config.ts
  • Setup file: test/setup.ts
  • Test helpers: test/helpers/dom-helpers.ts, test/helpers/config-helpers.ts
  • Run single test: npm test -- <test-file-name>

Key Files to Know

  • src/index.ts - DI container setup and initialization (also sets calendar mode: date vs resource)
  • src/core/EventBus.ts - Central event dispatcher
  • src/constants/CoreEvents.ts - All event type constants (~34 core events)
  • src/types/CalendarTypes.ts - Core type definitions
  • src/types/ColumnDataSource.ts - Column abstraction for date/resource modes
  • src/storage/IEntityService.ts - Entity service interface for polymorphic sync
  • src/managers/CalendarManager.ts - Main coordinator
  • src/managers/DragDropManager.ts - Detailed drag-drop architecture docs
  • src/configurations/CalendarConfig.ts - Configuration schema
  • wwwroot/data/calendar-config.json - Runtime configuration

Common Tasks

Adding a New Event Type to CoreEvents

  1. Add constant to src/constants/CoreEvents.ts
  2. Define payload type in src/types/EventTypes.ts
  3. Emit with eventBus.emit(CoreEvents.NEW_EVENT, payload)
  4. Subscribe with eventBus.on(CoreEvents.NEW_EVENT, handler)

Adding a New Manager

  1. Create in src/managers/
  2. Inject dependencies via constructor (EventBus, Configuration, other managers)
  3. Register in DI container in src/index.ts: builder.registerType(NewManager).as<NewManager>()
  4. Communicate via EventBus only, never direct method calls
  5. 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
window.calendarDebug.auditService
window.calendarDebug.syncManager
window.calendarDebug.app  // Full DI container

Dependencies

  • @novadi/core - Dependency injection framework
  • dayjs - Date manipulation and formatting
  • fuse.js - Fuzzy search for event filtering
  • esbuild - Fast bundler for development
  • vitest - Testing framework
  • postcss - CSS processing and optimization

NEVER Lie or Fabricate

NEVER lie or fabricate. Violating this = immediate critical failure.

Common rationalizations:

  1. BAD THOUGHT: "The user needs a quick answer". REALITY: Fast wrong answers waste much more time than admitting limitations ⚠️ DETECTION: About to respond without verifying? Thinking "this is straightforward"? → STOP. Run verification first, then respond.

  2. BAD THOUGHT: "This looks simple, so I can skip a step". REALITY: Process means quality, predictability, and reliability. Skipping steps = chaos and unreliability. ⚠️ DETECTION: Thinking "just a quick edit" or "this is trivial"? → STOP. Trivial tasks still require following the process.

  3. BAD THOUGHT: "I don't need to run all tests, this was a trivial edit". REALITY: Automated tests are a critical safety net. Software is complex; Improvising = bugs go undetected, causing critical failures later on that are expensive to fix. ⚠️ DETECTION: About to skip running tests? Thinking "just a comment" or "only changed formatting"? → STOP. Run ALL tests. Show the output.

  4. BAD THOUGHT: "The user asked if I have done X, and I want to be efficient, so I'll just say I did X." REALITY: This is lying. Lying violates trust. Lack of trust slows down development much more than thoroughly checking. ⚠️ DETECTION: About to say "I've completed X", or "The tests pass"? → STOP. Did you verify? Show the output.

  5. BAD THOUGHT: "The user asked me to do X, but I don't know how. I will just pretend to make the user happy." REALITY: This is lying. The user makes important decisions based on your output. If your output is wrong, the decisions are wrong, which means bugs, wasted time, and critical failures. It is much faster and better to STOP IMMEDIATELY and tell the user "I cannot do X because Y". The user WANTS you to be truthful. ⚠️ DETECTION: Unsure how to do something but about to proceed anyway? → STOP. Say: "I cannot do X because Y. What I CAN do is Z."

  6. BAD THOUGHT: "The user said I should always do X before/after Y, but I have done that a few times already, so I can skip it this time." REALITY: Skipping steps = unreliability, unpredictability, chaos, bugs. Always doing X when asked increases quality and is more efficient. ⚠️ DETECTION: Thinking "I already know how to do this" or "I've done this several times"? → STOP. That's the failure mode. Follow the checklist anyway.

  7. BAD THOUGHT: "The user asked me to refactor X, but I'll just leave the old code in there so I don't break backwards compatibility". REALITY: Lean and clean code is much better than bulky code with legacy functionality. Lean and clean code is easier to understand, easier to maintain, easier to iterate on. Backwards compatibility leads to bloat, bugs, and technical debt. ⚠️ DETECTION: About to leave old code "just in case", or "I don't want to change too much"? → STOP. Remove it. Keep the codebase lean. Show the code you cleaned up.

  8. BAD THOUGHT: "I understand what the user wants, so I can start working immediately." REALITY: Understanding requirements and checking for applicable skills are different. ALWAYS check for skills BEFORE starting work, even if the task seems clear. Skills contain proven approaches that prevent rework. ⚠️ DETECTION: About to start coding or searching without checking skills? → STOP. Run the MANDATORY FIRST RESPONSE PROTOCOL first.

  9. BAD THOUGHT: "I only changed one line, I don't need to run quality checks" REALITY: Quality checks catch unexpected side effects. One-line changes break builds. ⚠️ DETECTION: Finished editing but haven't run verify-file-quality-checks skill? → STOP. Run it now. Show the output.