> ## Documentation Index
> Fetch the complete documentation index at: https://ihub.learnille.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Instructor Course Editor Architecture

> Migrated from the previous Learnille internal documentation hub

**Version:** 1.0
**Last Updated:** 2025-11-02
**Maintainers:** Engineering Team

***

## Table of Contents

1. [Overview](#overview)
2. [Architecture Principles](#architecture-principles)
3. [System Components](#system-components)
4. [State Management](#state-management)
5. [Data Flow](#data-flow)
6. [Navigation System](#navigation-system)
7. [Step Handler Registry Pattern](#step-handler-registry-pattern)
8. [Entity Lifecycle Management](#entity-lifecycle-management)
9. [Persistence Strategy](#persistence-strategy)
10. [Error Handling](#error-handling)
11. [Performance Optimizations](#performance-optimizations)
12. [Key Design Patterns](#key-design-patterns)
13. [Extension Points](#extension-points)

***

## Overview

The Instructor Course Editor is a **multi-step wizard** for creating and editing courses. It implements a sophisticated state-driven architecture using Zustand for state management, with a step-based navigation system, optimistic UI updates, and robust error handling.

### Key Features

* **5-Step Wizard**: Basic Info → Advanced Info → Curriculum → Pricing → Publish
* **Optimistic Updates**: UI responds immediately, syncs async with backend
* **Persistence**: Critical data persists across sessions
* **Permission Control**: Owner/manager authorization checks
* **Unsaved Changes Protection**: Prevents data loss on navigation
* **Real-time Validation**: Per-step validation with error tracking
* **Entity Status Tracking**: NEW → UPDATED → SYNCED → DELETED states

### Technology Stack

* **State Management**: Zustand with slices pattern
* **Forms**: React Hook Form + Zod validation
* **Data Fetching**: React Query
* **UI Framework**: Chakra UI v3
* **Navigation**: React Router v6
* **Animations**: Framer Motion

***

## Architecture Principles

### 1. **Separation of Concerns**

* **Parent Component**: Initial data fetch, routing, permission checks
* **Step Components**: Individual UI, validation, save logic
* **Store Slices**: Domain-specific state management
* **Hooks**: Reusable logic encapsulation

### 2. **Single Source of Truth**

* Zustand store is the canonical source for all editor state
* React Query for server state caching
* LocalStorage for session persistence

### 3. **Loose Coupling**

* Steps don't know about each other
* Navigation logic separated from step logic
* Save handlers registered dynamically via registry pattern

### 4. **Progressive Enhancement**

* Works with partial data
* Handles network failures gracefully
* Supports continue-previous-work flow

### 5. **Type Safety**

* Full TypeScript coverage
* Zod schemas for runtime validation
* Interface contracts between layers

***

## System Components

### Component Hierarchy

```
CourseEditor (wrapper with error boundary)
└── CourseEditorContent
    ├── ContinueWorkModal
    ├── UnauthorizedAccessModal
    └── Tabs
        ├── TabList (navigation)
        └── TabPanels (step content)
            ├── BasicInformation
            ├── AdvanceInformation
            ├── Curriculum
            ├── Pricing
            └── PublishCourse
```

### Core Files

#### **Pages** (`instructor/src/pages/editor/`)

* `CourseEditor.tsx` - Main wrapper with data fetching
* `BasicInformation.tsx` - Course title, category, level, language
* `AdvanceInformation.tsx` - Thumbnail, video, description, requirements
* `Curriculum.tsx` - Course structure (sections/subsections/items)
* `Pricing.tsx` - Pricing configuration
* `PublishCourse.tsx` - Final review and submission

#### **Store Slices** (`instructor/src/stores/course-editor/`)

* `index.ts` - Main store creator with persistence
* `state.ts` - Initial state definition
* `types.ts` - Initial values for each step
* `navigation.slice.ts` - Step navigation logic
* `form.slice.ts` - Form state management
* `curriculum.slice.ts` - Course structure operations
* `sync.slice.ts` - Backend synchronization
* `step.slice.ts` - Step completion tracking
* `step-handler-registry.ts` - Handler registry implementation

#### **Hooks** (`instructor/src/hooks/`)

* `useEditorNavigation.ts` - Enhanced navigation with validation
* `useCourseEditorInitialization.ts` - Editor initialization logic
* `useStepSubmission.ts` - Step handler registration

***

## State Management

### Zustand Store Structure

```typescript theme={null}
interface CourseEditorStore {
  // Mode
  mode: EditorMode.CREATE | EditorMode.EDIT;
  courseId: string | null;

  // Step Data
  basicInfo: IBasicInfo;
  advancedInfo: IAdvancedInfo;
  sections: Record<string, Section>;
  subsections: Record<string, Subsection>;
  items: Record<string, CourseItem>;
  pricing: Pricing;
  publish: Publish;

  // UI State
  currentIndex: number;
  isSubmitting: boolean;
  isLoading: boolean;
  isDisabled: boolean;

  // Step Tracking
  stepStates: Record<StepKey, StepState>;
  navigationState: NavigationState;
  optimisticUpdates: Record<string, any>;

  // Registry
  stepHandlerRegistry: StepHandlerRegistry;
}
```

### Slice Pattern

The store is composed of **5 specialized slices**:

#### **1. Navigation Slice**

```typescript theme={null}
interface NavigationSlice {
  _next: (step?: number) => void;
  _prev: (step?: number) => void;
  _jump: (step: number) => void;
  refreshStepAccessibility: () => void;
}
```

**Responsibilities:**

* Step index management
* Accessibility validation
* Mode switching (CREATE → EDIT)
* Step completion checking

#### **2. Form Slice**

```typescript theme={null}
interface FormSlice {
  setBasicInfo: (field: keyof IBasicInfo, value: any) => void;
  setAdvancedInfo: (field: keyof IAdvancedInfo, value: any) => void;
  setPricing: (field: keyof Pricing, value: any) => void;
  setPublish: (field: keyof Publish, value: any) => void;
  validateCurrentStep: () => ValidationResult;
  validateStepData: (stepKey: StepKey) => ValidationResult;
}
```

**Responsibilities:**

* Field-level updates
* Real-time validation
* Form state tracking

#### **3. Curriculum Slice**

```typescript theme={null}
interface CurriculumSlice {
  // CRUD Operations
  addSection: (courseId: string, title?: string) => MutationInfo;
  updateSection: (id: string, updates: Partial<Section>) => MutationInfo;
  removeSection: (id: string) => MutationInfo;

  addSubsection: (sectionId: string, title?: string) => MutationInfo;
  updateSubsection: (id: string, updates: Partial<Subsection>) => MutationInfo;
  removeSubsection: (sectionId: string, id: string) => MutationInfo;

  addItem: (subsectionId: string, type: CourseItemType, title?: string) => MutationInfo;
  updateItem: (id: string, updates: Partial<CourseItem>) => MutationInfo;
  removeItem: (subsectionId: string, id: string) => MutationInfo;

  // Reordering
  moveSectionUpDown: (id: string, direction: 'up' | 'down') => Array<MutationInfo>;
  moveSubsectionUpDown: (sectionId: string, id: string, direction: 'up' | 'down') => Array<MutationInfo>;
  moveItemUpDown: (subsectionId: string, id: string, direction: 'up' | 'down') => Array<MutationInfo>;
}
```

**Responsibilities:**

* Hierarchical structure management
* Optimistic UI updates with temp IDs
* Order management with automatic reindexing
* Cascade deletion

#### **4. Sync Slice**

```typescript theme={null}
interface SyncSlice {
  populateStore: (data: CourseResponseDto) => void;
  populateCurriculum: (data: CurriculumDto, options?: { replaceExisting?: boolean }) => void;

  confirmSectionCreation: (tempId: string, serverData: Section) => void;
  confirmSectionUpdate: (id: string, serverData: Section) => void;
  confirmSectionDeletion: (id: string) => void;

  // Similar for subsections and items...

  setEntitySyncError: (type: string, id: string, error: any, action: string, payload: any) => void;
}
```

**Responsibilities:**

* Initial data hydration
* Temp ID → Real ID replacement
* Entity status updates (SYNCED/ERROR)
* Error tracking and recovery

#### **5. Step Slice**

```typescript theme={null}
interface StepSlice {
  markStepDirty: (stepKey: StepKey) => void;
  markStepSaving: (stepKey: StepKey, isSaving: boolean) => void;
  markStepSaved: (stepKey: StepKey, data?: any) => void;
  markStepError: (stepKey: StepKey, errors: Record<string, string>) => void;

  checkStepCompletion: (stepKey: StepKey) => boolean;
  getStepCompletionStatus: (stepKey: StepKey) => 'pending' | 'current' | 'completed' | 'error';

  hasUnsavedChanges: () => boolean;
  getUnsavedSteps: () => StepKey[];

  initializeStepStatesFromCourse: () => void;
}
```

**Responsibilities:**

* Step state lifecycle
* Completion tracking
* Unsaved changes detection
* Step accessibility calculation

***

## Data Flow

### 1. **Initial Load (Edit Mode)**

```
User navigates to /editor/:courseId/basic
        ↓
CourseEditor.tsx mounts
        ↓
React Query fetches course data
  - CourseService.getCourseById(courseId)
  - CourseService.getNormalizedCurriculum(courseId)
        ↓
populateStore(courseData)
  - Hydrates basicInfo, advancedInfo, pricing, publish
        ↓
populateCurriculum(curriculumData)
  - Hydrates sections, subsections, items
  - Marks all entities as SYNCED
        ↓
initializeStepStatesFromCourse()
  - Validates each step's data
  - Sets completion status
  - Calculates accessibility
        ↓
Permission check
  - isOwner || isManager?
  - If false: Show unauthorized modal
        ↓
Editor ready
```

### 2. **Step Save Flow**

```
User fills form → Clicks "Next"
        ↓
useEditorNavigation.navigateToStep(nextIndex)
        ↓
saveCurrentStep()
        ↓
Get handler from registry
        ↓
handler.validateForm() (if exists)
        ↓
handler.onSave()
        ↓
Step component's onSubmit()
  - Calls CourseService.updateCourse(courseId, data)
  - Uses withRetry for resilience
        ↓
On success:
  - markStepSaved(stepKey)
  - Update store with server response
  - Invalidate React Query cache
        ↓
On failure:
  - markStepError(stepKey, errors)
  - Show toast notification
  - Stay on current step
        ↓
Navigation proceeds if successful
```

### 3. **Curriculum Entity Creation Flow**

```
User clicks "Add Section"
        ↓
addSection(courseId, "New Section")
        ↓
Generate tempId: "temp-${uuid}"
        ↓
Create optimistic section in store
  {
    id: tempId,
    title: "New Section",
    entityStatus: EntityStatus.NEW,
    ...
  }
        ↓
UI updates immediately
        ↓
Component triggers mutation
  CourseSectionService.createSection(payload)
        ↓
On success (server returns real ID):
  confirmSectionCreation(tempId, serverSection)
    - Remove temp entry
    - Add real entry with entityStatus: SYNCED
        ↓
On failure:
  setEntitySyncError('section', tempId, error, 'create', payload)
    - Mark as SYNC_ERROR
    - Store payload for retry
```

### 4. **Curriculum Entity Update Flow**

```
User edits section title
        ↓
updateSection(sectionId, { title: "New Title" })
        ↓
Update store immediately
  entityStatus: UPDATED (if was SYNCED)
  entityStatus: NEW (if was NEW - not yet saved)
        ↓
UI updates immediately
        ↓
Component triggers mutation
  CourseSectionService.updateSection(id, payload)
        ↓
On success:
  confirmSectionUpdate(sectionId, serverSection)
    - Update with server data
    - entityStatus: SYNCED
        ↓
On failure:
  setEntitySyncError('section', sectionId, error, 'update', payload, originalData)
    - Restore original data (optional)
    - Mark as SYNC_ERROR
```

### 5. **Curriculum Entity Deletion Flow**

```
User clicks "Delete Section"
        ↓
removeSection(sectionId)
        ↓
Check entityStatus:

If NEW (never saved):
  - Remove from store immediately
  - Remove all child subsections/items
  - Reorder remaining sections
  - Return { clientOnly: true }
        ↓
If SYNCED or UPDATED:
  - Mark as DELETED in store
  - Hide from UI
  - Return { idToDelete: sectionId }
        ↓
Component triggers mutation
  CourseSectionService.deleteSection(id)
        ↓
On success:
  confirmSectionDeletion(sectionId)
    - Remove from store
    - Remove all children
    - Reorder remaining
        ↓
On failure:
  setEntitySyncError('section', sectionId, error, 'delete', {}, originalData)
    - Restore to original state
    - Show error
```

***

## Navigation System

### Navigation Hook (`useEditorNavigation`)

The `useEditorNavigation` hook is the **central navigation controller** for the editor.

#### Core Methods

```typescript theme={null}
interface UseEditorNavigationReturn {
  // Navigation
  navigateToStep: (targetStep: EditorStep | number) => Promise<boolean>;
  navigateNext: () => Promise<boolean>;
  navigatePrev: () => Promise<boolean>;

  // Save operations
  saveCurrentStep: () => Promise<boolean>;
  saveAndNavigateNext: () => Promise<boolean>;

  // State queries
  isStepAccessible: (step: EditorStep | number) => boolean;
  getStepStatus: (step: EditorStep | number) => 'current' | 'completed' | 'pending' | 'error';
  getStepAccessibilityReason: (step: EditorStep | number) => string | null;

  // Unsaved changes
  hasUnsavedChanges: () => boolean;
  getUnsavedSteps: () => StepKey[];

  // Route blocking
  pendingNavigation: { targetStep: number; saveFirst: boolean } | null;
  confirmNavigation: () => void;
  cancelNavigation: () => void;
  saveAndNavigate: () => Promise<void>;

  // Modal props
  modalProps: {
    isOpen: boolean;
    onClose: () => void;
    onConfirm: () => void;
    onDiscard: () => void;
    isSaving: boolean;
  };
}
```

#### Navigation Rules

```typescript theme={null}
// Forward navigation: Auto-saves current step
if (targetIndex > currentIndex) {
  const saveSuccess = await saveCurrentStep();
  if (!saveSuccess) return false;
}

// Step 0 is always accessible
if (targetIndex === 0) return true;

// Backward navigation: Always allowed
if (targetIndex < currentIndex) return true;

// Forward navigation requires:
// 1. courseId exists (created in step 0)
if (!courseId) return false;

// 2. Previous steps are completed or have valid data
for (let i = 0; i < targetIndex; i++) {
  const stepKey = getStepKeyByIndex(i);
  if (!checkStepCompletion(stepKey) && !courseId) {
    return false;
  }
}

// 3. Current step has no errors
const currentStepState = stepStates[currentStepKey];
if (currentStepState.hasErrors) return false;
```

#### URL Synchronization

```typescript theme={null}
useEffect(() => {
  const buildEditorUrl = (courseId: string | null, stepName: string) => {
    if (courseId) return `/editor/${courseId}/${stepName}`;
    return `/editor/new/${stepName}`;
  };

  const expectedUrl = buildEditorUrl(courseId, stepValues[currentIndex]);
  if (currentPath !== expectedUrl) {
    navigate(expectedUrl, { replace: true });
  }
}, [currentIndex, courseId]);
```

#### Unsaved Changes Protection

```typescript theme={null}
const blocker = useBlocker(
  ({ currentLocation, nextLocation }) =>
    hasUnsavedChanges() &&
    currentLocation.pathname !== nextLocation.pathname
);

useEffect(() => {
  if (blocker.state === 'blocked') {
    // Show modal: Save, Discard, or Cancel?
    onOpen();
    setPendingNavigation({ targetStep: -1, saveFirst: false });
  }
}, [blocker]);
```

### Step States

Each step tracks its own state:

```typescript theme={null}
interface StepState {
  // Validation
  isValid: boolean;
  hasErrors: boolean;
  errors: Record<string, string>;

  // Data
  hasValidData: boolean;

  // Lifecycle
  isDirty: boolean;
  isSaving: boolean;
  lastSaved: Date | null;

  // Accessibility
  isAccessible: boolean;
  completionStatus: 'pending' | 'current' | 'completed' | 'error';
}
```

### Visual Feedback

Steps are visually styled based on their state:

```typescript theme={null}
const getStepColor = () => {
  if (isDisabled) return 'gray.400';
  if (stepStatus === 'completed') return 'green.500';
  if (stepStatus === 'current') return 'blue.500';
  if (stepStatus === 'error') return 'red.500';
  return 'inherit';
};

const getTabStyles = () => {
  if (stepStatus === 'completed') {
    return {
      borderLeft: '3px solid',
      borderLeftColor: 'green.500',
    };
  }
  if (stepStatus === 'current') {
    return {
      bg: 'blue.50',
      borderLeft: '3px solid',
      borderLeftColor: 'blue.500',
    };
  }
  if (stepStatus === 'error') {
    return {
      borderLeft: '3px solid',
      borderLeftColor: 'red.500',
    };
  }
  return {};
};
```

***

## Step Handler Registry Pattern

### Overview

The **Step Handler Registry** decouples navigation logic from step-specific business logic. Each step registers a handler that implements a common interface, allowing the navigation system to call step-specific functions without knowing implementation details.

### Handler Interface

```typescript theme={null}
interface StepHandler {
  // Save/Discard
  onSave: () => Promise<StepSaveResult>;
  onDiscard: () => void;

  // Validation
  validateForm?: () => boolean | Promise<boolean>;

  // State
  readonly hasUnsavedChanges: boolean;
  readonly hasErrors: boolean;
  readonly isSaving: boolean;
}

interface StepSaveResult {
  success: boolean;
  errors?: Record<string, string>;
  queryKeysToInvalidate?: Array<unknown[]>;
}
```

### Registry Implementation

```typescript theme={null}
class StepHandlerRegistry {
  private handlers = new Map<StepKey, StepHandler>();

  register(stepKey: StepKey, handler: StepHandler): void {
    this.handlers.set(stepKey, handler);
  }

  unregister(stepKey: StepKey): void {
    this.handlers.delete(stepKey);
  }

  get(stepKey: StepKey): StepHandler | undefined {
    return this.handlers.get(stepKey);
  }
}
```

### Step Registration Hook

```typescript theme={null}
const useStepSubmission = ({
  stepKey,
  onSave,
  validateForm,
  onDiscard,
  hasUnsavedChanges,
  hasErrors,
}: UseStepSubmissionOptions): UseStepSubmissionReturn => {
  const [isSaving, setIsSaving] = useState(false);
  const stepHandlerRegistry = useCourseEditorStore((state) => state.stepHandlerRegistry);

  const handleSave = useCallback(async (): Promise<StepSaveResult> => {
    setIsSaving(true);
    try {
      // Run validation if provided
      if (validateForm) {
        const isValid = await Promise.resolve(validateForm());
        if (!isValid) {
          return { success: false, errors: { general: 'Validation failed' } };
        }
      }

      // Execute save
      const result = await onSave();

      if (result.success) {
        useCourseEditorStore.getState().markStepSaved(stepKey);
      } else {
        useCourseEditorStore.getState().markStepError(stepKey, result.errors || {});
      }

      return result;
    } finally {
      setIsSaving(false);
    }
  }, [stepKey, onSave, validateForm]);

  // Register handler on mount
  useEffect(() => {
    const handler: StepHandler = {
      onSave: handleSave,
      onDiscard,
      validateForm,
      get isSaving() { return isSaving; },
      get hasUnsavedChanges() { return hasUnsavedChanges; },
      get hasErrors() { return hasErrors; },
    };

    stepHandlerRegistry.register(stepKey, handler);

    return () => {
      stepHandlerRegistry.unregister(stepKey);
    };
  }, [stepKey, stepHandlerRegistry, handleSave]);

  return { isSaving };
};
```

### Usage in Step Components

```typescript theme={null}
// BasicInformation.tsx
const BasicInformation = () => {
  const { basicInfo, setBasicInfo, courseId, mode, setCourseId } = useCourseEditorStore();
  const navigation = useEditorNavigation();

  const {
    formState: { errors, isDirty, isValid },
    getValues,
    reset,
  } = useForm<IBasicInfo>({
    resolver: zodResolver(basicInfoSchema),
    defaultValues: basicInfo,
  });

  const onSubmit = useCallback(async (): Promise<StepSaveResult> => {
    const data = getValues();

    try {
      if (mode === EditorMode.CREATE) {
        const res = await CourseService.createCourse(data);
        setCourseId(res.id);
        return { success: true };
      } else {
        await CourseService.updateCourse(courseId, data);
        return { success: true, queryKeysToInvalidate: [['course', courseId, 'details']] };
      }
    } catch (error) {
      return { success: false, errors: { general: error.message } };
    }
  }, [mode, courseId, getValues]);

  const handleDiscard = useCallback(() => {
    reset(basicInfo);
  }, [basicInfo, reset]);

  useStepSubmission({
    stepKey: 'basic',
    onSave: onSubmit,
    onDiscard: handleDiscard,
    hasUnsavedChanges: isDirty,
    hasErrors: !isValid,
  });

  return (
    <form>
      {/* Form fields */}
      <ButtonArrow onClick={() => navigation.saveAndNavigateNext()}>
        Next
      </ButtonArrow>
    </form>
  );
};
```

### Benefits

1. **Decoupling**: Navigation doesn't know about step internals
2. **Testability**: Each handler can be tested independently
3. **Flexibility**: Easy to add new steps without modifying navigation
4. **Type Safety**: Common interface ensures consistency
5. **Reusability**: Handler logic can be shared across steps

***

## Entity Lifecycle Management

### Entity Status Enum

```typescript theme={null}
enum EntityStatus {
  NEW = 'new',           // Created locally, not yet saved
  UPDATED = 'updated',   // Modified after being synced
  SYNCED = 'synced',     // In sync with backend
  DELETED = 'deleted',   // Marked for deletion
  SYNC_ERROR = 'error',  // Failed to sync
}
```

### Entity Structure

```typescript theme={null}
interface Section {
  id: string;
  title: string;
  courseId: UUID;
  order: number;
  isActive: boolean;
  subsectionIds: string[];

  // Lifecycle fields
  entityStatus: EntityStatus;
  syncErrorDetails?: {
    errorMessage: string;
    attemptedAction: 'create' | 'update' | 'delete';
    payload: any;
    originalDataIfUpdate?: Section;
  };

  // Timestamps
  createdAt: Date;
  updatedAt: Date;
}
```

### Lifecycle Transitions

```
                    ┌──────────────┐
                    │  User Action │
                    └──────┬───────┘
                           │
                           ▼
            ┌──────────────────────────┐
            │   Create entity in UI    │
            │   entityStatus: NEW      │
            │   id: temp-${uuid}       │
            └──────────┬───────────────┘
                       │
                       ▼
            ┌──────────────────────────┐
            │  Trigger API mutation    │
            └──────┬────────────┬──────┘
                   │            │
        ┌──────────▼────┐  ┌───▼──────────┐
        │   Success     │  │    Failure    │
        │               │  │               │
        │ Replace temp  │  │ Mark as ERROR │
        │ with real ID  │  │ Store payload │
        │ SYNCED status │  │ Show retry UI │
        └───────────────┘  └───────────────┘
```

### Update Flow

```
Entity with status: SYNCED
        │
        ▼
User modifies field
        │
        ▼
entityStatus = UPDATED
updatedAt = new Date()
        │
        ▼
Trigger API mutation
        │
   ┌────┴────┐
   │         │
Success   Failure
   │         │
SYNCED    SYNC_ERROR
          Store original data
```

### Delete Flow

```
Entity marked for deletion
        │
   ┌────┴────┐
   │         │
  NEW    SYNCED/UPDATED
   │         │
   │         ▼
   │    entityStatus = DELETED
   │    Hide from UI
   │    Don't remove from store yet
   │         │
   │         ▼
   │    Trigger API mutation
   │         │
   │    ┌────┴────┐
   │    │         │
   │ Success   Failure
   │    │         │
   │    │    Restore entity
   │    │    Show error
   │    │
   └────┴────────▶ Remove from store
```

### Temporary IDs

```typescript theme={null}
// Generation
const tempId = `temp-${uuidv4()}`;

// Identification
const isTemporary = (id: string) => id.startsWith('temp-');

// Replacement after successful creation
confirmSectionCreation(tempId, serverSection) {
  const { [tempId]: _, ...restSections } = state.sections;
  return {
    sections: {
      ...restSections,
      [serverSection.id]: {
        ...serverSection,
        entityStatus: EntityStatus.SYNCED,
      },
    },
  };
}
```

### Error Recovery

```typescript theme={null}
// Sync error tracking
interface SyncErrorDetails {
  errorMessage: string;
  attemptedAction: 'create' | 'update' | 'delete';
  payload: any;
  originalDataIfUpdate?: Section | Subsection | CourseItem;
}

// Retry mechanism
const retryFailedEntity = async (entity: Section) => {
  const { syncErrorDetails } = entity;
  if (!syncErrorDetails) return;

  try {
    switch (syncErrorDetails.attemptedAction) {
      case 'create':
        const res = await CourseSectionService.createSection(syncErrorDetails.payload);
        confirmSectionCreation(entity.id, res);
        break;

      case 'update':
        await CourseSectionService.updateSection(entity.id, syncErrorDetails.payload);
        confirmSectionUpdate(entity.id, syncErrorDetails.payload);
        break;

      case 'delete':
        await CourseSectionService.deleteSection(entity.id);
        confirmSectionDeletion(entity.id);
        break;
    }
  } catch (error) {
    // Still failed, keep error state
    console.error('Retry failed:', error);
  }
};
```

***

## Persistence Strategy

### LocalStorage Persistence

Only **critical data** is persisted to avoid bloating localStorage:

```typescript theme={null}
export const useCourseEditorStore = create<CourseEditorStore>()(
  persist(
    storeCreator,
    {
      name: 'course-editor-storage',
      partialize: (state) => ({
        courseId: state.courseId,
        basicInfo: state.basicInfo,
        advancedInfo: state.advancedInfo,
        pricing: state.pricing,
        publish: state.publish,
      }),
    }
  )
);
```

**Not Persisted:**

* `sections`, `subsections`, `items` (curriculum)
* `stepStates` (recomputed on mount)
* `navigationState` (derived)
* `optimisticUpdates` (transient)
* UI state flags

**Rationale:**

* Curriculum can be large and is fetched from server
* Step states are derived from validated data
* Reduces localStorage quota usage
* Prevents stale curriculum data

### Continue Work Modal

When the editor mounts:

```typescript theme={null}
const useCourseEditorInitialization = () => {
  const [showContinueModal, setShowContinueModal] = useState(false);
  const [staleCourseTitle, setStaleCourseTitle] = useState<string | null>(null);

  useEffect(() => {
    const persistedCourseId = localStorage.getItem('course-editor-storage');
    if (!persistedCourseId) return;

    // Fetch server's lastModified
    const serverData = await CourseService.getCourseById(persistedCourseId);
    const localModified = localStorage.getItem('lastModified');

    if (serverData.updatedAt !== localModified) {
      setStaleCourseTitle(serverData.title);
      setShowContinueModal(true);
    }
  }, []);

  const continuePreviousWork = () => {
    // Keep localStorage state
    setShowContinueModal(false);
  };

  const startFresh = () => {
    // Clear localStorage
    localStorage.removeItem('course-editor-storage');
    resetStore();
    setShowContinueModal(false);
  };

  return { showContinueModal, continuePreviousWork, startFresh };
};
```

***

## Error Handling

### Multi-Layer Error Boundaries

#### **1. Application Level**

```typescript theme={null}
// CourseEditorErrorBoundary
<ErrorBoundary
  FallbackComponent={CourseEditorErrorFallback}
  onError={(error, info) => {
    console.error('Course Editor Error:', error, info);
    // Send to error tracking service
  }}
>
  <CourseEditorContent />
</ErrorBoundary>
```

#### **2. Step Level**

* React Hook Form validation errors
* Zod schema validation
* Displayed inline near fields

#### **3. API Level**

```typescript theme={null}
try {
  await CourseService.updateCourse(courseId, data);
} catch (error) {
  const message = handleError(error); // Utility to extract message
  toast({
    title: "Save Failed",
    description: message,
    status: "error",
  });
  return { success: false, errors: { general: message } };
}
```

#### **4. Navigation Guards**

```typescript theme={null}
if (!isStepAccessible(targetIndex)) {
  toast({
    title: "Cannot Navigate",
    description: getStepAccessibilityReason(targetIndex),
    status: "warning",
  });
  return false;
}
```

### Retry Logic

```typescript theme={null}
const withRetry = async <T>(
  fn: () => Promise<T>,
  operationName: string,
  options: {
    maxRetries?: number;
    baseDelay?: number;
    maxDelay?: number;
  } = {}
): Promise<T> => {
  const { maxRetries = 3, baseDelay = 1000, maxDelay = 5000 } = options;

  let lastError: Error;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;

      if (attempt < maxRetries) {
        const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay);
        console.log(`Retry ${attempt + 1}/${maxRetries} for ${operationName} after ${delay}ms`);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }

  throw lastError;
};
```

### Permission Errors

```typescript theme={null}
useEffect(() => {
  if (!fetchedCourseData || !profile.data) return;

  const isOwner = fetchedCourseData.instructor?.user?.id === profile.data.id;
  const isManager = fetchedCourseData.managers?.some(
    m => m.user?.id === profile.data.id
  );

  if (!isOwner && !isManager) {
    setIsDisabled(true);
    onOpen(); // Show UnauthorizedAccessModal
    return;
  }

  setIsDisabled(false);
}, [fetchedCourseData, profile.data]);
```

***

## Performance Optimizations

### 1. **Memoization**

```typescript theme={null}
// Memoize curriculum array computation
const curriculumArray = useMemo(() =>
  Object.values(sections)
    .filter(section => section.entityStatus !== 'deleted')
    .sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
    .map(section => ({
      ...section,
      subsections: Object.values(subsections)
        .filter(sub => sub.sectionId === section.id && sub.entityStatus !== 'deleted')
        .sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
    })),
  [sections, subsections, items]
);
```

### 2. **Shallow Equality Checking**

```typescript theme={null}
import { shallow } from 'zustand/shallow';

const { sections, subsections, items } = useCourseEditorStore(
  state => ({
    sections: state.sections,
    subsections: state.subsections,
    items: state.items
  }),
  shallow // Only re-render if references change
);
```

### 3. **Debounced Validation**

```typescript theme={null}
const debouncedValidate = debounce(() => {
  const result = validateCurrentStep();
  if (!result.success) {
    markStepError(currentStepKey, result.errors);
  } else {
    markStepClean(currentStepKey);
  }
}, 300);

// Called on field changes
const updateStepField = (stepKey, field, value) => {
  // Update immediately
  setBasicInfo(field, value);
  markStepDirty(stepKey);

  // Validate after debounce
  debouncedValidate();
};
```

### 4. **Conditional Queries**

```typescript theme={null}
// Only fetch curriculum if not already in store
const { data: curriculumData } = useQuery({
  queryKey: ['course', courseId, 'curriculum'],
  queryFn: () => CourseService.getNormalizedCurriculum(courseId),
  enabled: !!courseId && Object.keys(sections).length === 0,
  refetchOnWindowFocus: false,
});
```

### 5. **Optimistic UI Updates**

```typescript theme={null}
// UI updates immediately
addSection(courseId, title);

// Backend sync happens async
useMutation({
  mutationFn: (payload) => CourseSectionService.createSection(payload),
  onSuccess: (serverSection) => {
    confirmSectionCreation(tempId, serverSection);
  },
  onError: (error) => {
    setEntitySyncError('section', tempId, error, 'create', payload);
  },
});
```

### 6. **Selective Re-renders**

```typescript theme={null}
// Use memo to prevent unnecessary re-renders
const DebouncedCharacterCounter = memo(({ value, maxLength }) => {
  const [displayValue, setDisplayValue] = useState(value?.length || 0);

  useEffect(() => {
    const timer = setTimeout(() => {
      setDisplayValue(value?.length || 0);
    }, 100);
    return () => clearTimeout(timer);
  }, [value]);

  return <Text>{displayValue}/{maxLength}</Text>;
});
```

### 7. **Animation Performance**

```typescript theme={null}
// Use Framer Motion layout animations
<motion.div
  layout
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  exit={{ opacity: 0, y: -20 }}
  transition={{ duration: 0.2 }}
>
  {/* Content */}
</motion.div>
```

***

## Key Design Patterns

### 1. **Command Pattern** (Step Handlers)

* Encapsulates save/discard operations as objects
* Allows queuing, undo, and retry

### 2. **Registry Pattern** (Handler Registry)

* Dynamic registration of step handlers
* Loose coupling between navigation and steps

### 3. **Observer Pattern** (Zustand Subscriptions)

* Components subscribe to specific slices
* Automatic re-renders on state changes

### 4. **State Machine** (Step Progression)

* Finite states: pending → current → completed → error
* Well-defined transitions

### 5. **Optimistic Concurrency** (Entity Status)

* UI updates immediately
* Backend syncs asynchronously
* Handles conflicts gracefully

### 6. **Repository Pattern** (Services)

* CourseService, CourseSectionService, etc.
* Abstracts API communication

### 7. **Facade Pattern** (Navigation Hook)

* Simplifies complex navigation logic
* Single interface for step navigation

### 8. **Strategy Pattern** (Validation)

* Different validation strategies per step
* Zod schemas define validation rules

***

## Extension Points

### Adding a New Step

1. **Create step component** (`pages/editor/NewStep.tsx`)
2. **Add to initial state** (`stores/course-editor/types.ts`)
3. **Create form slice methods** (if needed)
4. **Add validation schema** (`utils/schema/course.schema.ts`)
5. **Register in navigation** (`EditorStep` enum)
6. **Add to tabs array** (`CourseEditor.tsx`)
7. **Implement step handler** (using `useStepSubmission`)

### Adding a New Entity Type

1. **Define interface** (`utils/types.ts`)
2. **Add to store state** (`stores/course-editor/state.ts`)
3. **Create CRUD methods** (new or existing slice)
4. **Add sync methods** (`sync.slice.ts`)
5. **Create service** (`services/new-entity.service.ts`)
6. **Build UI component** (`components/course/NewEntity.tsx`)

### Custom Validation

```typescript theme={null}
// Add to schema
export const customStepSchema = z.object({
  field: z.string().refine(
    (val) => customValidationLogic(val),
    { message: "Custom validation failed" }
  ),
});

// Use in step
const { formState: { errors }, trigger } = useForm({
  resolver: zodResolver(customStepSchema),
});
```

### Additional Hooks

```typescript theme={null}
// Example: Auto-save hook
const useAutoSave = (stepKey: StepKey, interval: number = 60000) => {
  const saveCurrentStep = useEditorNavigation().saveCurrentStep;

  useEffect(() => {
    const timer = setInterval(() => {
      saveCurrentStep();
    }, interval);

    return () => clearInterval(timer);
  }, [stepKey, saveCurrentStep, interval]);
};
```

***

## Conclusion

The Instructor Course Editor demonstrates a sophisticated, production-ready architecture that balances:

* **User Experience**: Instant feedback, clear progression, unsaved changes protection
* **Data Integrity**: Validation at multiple levels, optimistic updates with rollback
* **Performance**: Memoization, shallow equality, debounced validation
* **Maintainability**: Clear separation of concerns, typed interfaces, extensible patterns
* **Resilience**: Retry logic, error boundaries, graceful degradation

This architecture can serve as a template for other multi-step wizards and complex form flows in the application.

***

**Document History:**

* **v1.0** (2025-11-02): Initial architecture documentation


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.