quarto-review-extension

UIModule Decomposition Plan

Status: πŸ“‹ Planning Phase Priority: CRITICAL (Highest ROI Refactoring) Estimated Effort: 40-60 hours Risk Level: Very Low (Reversible, Well-Tested)


Executive Summary

The UIModule (src/modules/ui/index.ts) is a monolithic 2,866-line class with 102+ methods handling 7+ distinct concerns. This plan outlines a systematic decomposition into 5 focused classes following SOLID principles.

Current State:

Target State:


Problem Statement

Current Issues

  1. Cognitive Overload: 2,866 lines is too large to understand quickly
  2. Multiple Responsibilities: Editor management, comments, toolbar, state, events, DOM manipulation, persistence
  3. Testing Difficulty: Hard to test in isolation
  4. Tight Coupling: Changes ripple across unrelated functionality
  5. Merge Conflicts: High probability with multiple developers
  6. Onboarding Friction: New developers spend 2-3 days understanding the module

Impact


Proposed Architecture

Class Decomposition Strategy

Break UIModule into 5 focused classes:

UIModule (index.ts) - Thin orchestration layer
β”œβ”€β”€ EditorManager - Editor lifecycle and state management
β”œβ”€β”€ CommentCoordinator - Comment UI and interactions
β”œβ”€β”€ ToolbarController - Toolbar and action handlers
β”œβ”€β”€ DOMRenderer - DOM manipulation and updates
└── StateManager - UI state persistence and sync

Class Responsibilities

1. UIModule (Orchestrator)

File: src/modules/ui/index.ts (NEW - slim version) Lines: ~200-300 Responsibility: Coordinate between specialized classes

Public API:

class UIModule {
  constructor(config: UIModuleConfig);

  // Lifecycle
  async initialize(): Promise<void>;
  destroy(): void;

  // Mode management
  enterReviewMode(): void;
  exitReviewMode(): void;

  // Delegation methods (thin wrappers)
  editElement(elementId: string): void;
  addComment(elementId: string, content: string): void;
  refresh(): void;
}

Collaborators:


2. EditorManager

File: src/modules/ui/EditorManager.ts (NEW) Lines: ~500-600 Responsibility: Manage Milkdown editor lifecycle and state

Public API:

class EditorManager {
  // Editor lifecycle
  openEditor(elementId: string, content: string): Promise<void>;
  closeEditor(save: boolean): Promise<void>;

  // Editor state
  isEditorOpen(): boolean;
  getActiveElementId(): string | null;

  // Content management
  getEditorContent(): string;
  setEditorContent(content: string): void;

  // Editor configuration
  configureEditor(options: EditorOptions): void;

  // Callbacks
  onEditorSave(callback: (elementId: string, content: string) => void): void;
  onEditorCancel(callback: () => void): void;
}

Extracted Methods (from current UIModule):

Dependencies:


3. CommentCoordinator

File: src/modules/ui/CommentCoordinator.ts (NEW) Lines: ~400-500 Responsibility: Handle all comment-related UI and interactions

Public API:

class CommentCoordinator {
  // Comment rendering
  renderComments(comments: Comment[]): void;
  clearComments(): void;

  // Comment interactions
  addComment(elementId: string, content: string): void;
  editComment(commentId: string): void;
  deleteComment(commentId: string): void;
  resolveComment(commentId: string): void;

  // Comment UI state
  highlightComment(commentId: string): void;
  scrollToComment(commentId: string): void;

  // Callbacks
  onCommentAdded(callback: (comment: Comment) => void): void;
  onCommentDeleted(callback: (commentId: string) => void): void;
}

Extracted Methods:

Dependencies:


4. ToolbarController

File: src/modules/ui/ToolbarController.ts (NEW) Lines: ~300-400 Responsibility: Manage toolbar UI and action handlers

Public API:

class ToolbarController {
  // Toolbar lifecycle
  createToolbar(): HTMLElement;
  destroyToolbar(): void;

  // Toolbar state
  updateToolbarState(state: ToolbarState): void;
  enableAction(action: ToolbarAction): void;
  disableAction(action: ToolbarAction): void;

  // Action handlers
  onUndo(callback: () => void): void;
  onRedo(callback: () => void): void;
  onExport(callback: () => void): void;
  onSettings(callback: () => void): void;

  // Toolbar visibility
  showToolbar(): void;
  hideToolbar(): void;
}

Extracted Methods:

Dependencies:


5. DOMRenderer

File: src/modules/ui/DOMRenderer.ts (NEW) Lines: ~400-500 Responsibility: Handle all DOM manipulation and updates

Public API:

class DOMRenderer {
  // Element updates
  updateElement(elementId: string, content: string): void;
  highlightElement(elementId: string): void;
  removeHighlight(elementId: string): void;

  // Batch updates
  batchUpdate(updates: ElementUpdate[]): void;

  // Visual feedback
  showLoadingIndicator(target: HTMLElement): void;
  hideLoadingIndicator(): void;
  flashElement(elementId: string): void;

  // DOM queries
  getElement(elementId: string): HTMLElement | null;
  getAllEditableElements(): HTMLElement[];
}

Extracted Methods:

Dependencies:


6. StateManager

File: src/modules/ui/StateManager.ts (NEW) Lines: ~200-300 Responsibility: Manage UI state persistence and synchronization

Public API:

class StateManager {
  // State persistence
  saveState(key: string, value: any): void;
  loadState(key: string): any;
  clearState(key: string): void;

  // State synchronization
  syncState(state: UIState): void;
  getState(): UIState;

  // State observers
  onChange(callback: (state: UIState) => void): void;

  // State validation
  validateState(state: UIState): boolean;
}

Extracted Methods:

Dependencies:


Implementation Plan

Phase 1: Foundation (Week 1, 12-16 hours)

Goals:

Tasks:

  1. βœ… Create new file structure:
    src/modules/ui/
    β”œβ”€β”€ index.ts (slim orchestrator)
    β”œβ”€β”€ EditorManager.ts
    β”œβ”€β”€ CommentCoordinator.ts
    β”œβ”€β”€ ToolbarController.ts
    β”œβ”€β”€ DOMRenderer.ts
    β”œβ”€β”€ StateManager.ts
    └── types/
        β”œβ”€β”€ UIModuleTypes.ts
        β”œβ”€β”€ EditorTypes.ts
        β”œβ”€β”€ CommentTypes.ts
        └── ToolbarTypes.ts
    
  2. βœ… Define TypeScript interfaces for each class
  3. βœ… Extract StateManager (least dependencies, easiest to isolate)
  4. βœ… Extract DOMRenderer (no business logic, pure DOM operations)
  5. βœ… Write unit tests for extracted classes

Success Criteria:


Phase 2: Core Extraction (Week 2, 16-20 hours)

Goals:

Tasks:

  1. βœ… Extract EditorManager
    • Move editor lifecycle methods
    • Create clean Milkdown integration
    • Handle editor events
    • Test editor open/close/save workflows
  2. βœ… Extract ToolbarController
    • Move toolbar creation logic
    • Extract action handlers
    • Implement toolbar state management
    • Test all toolbar actions
  3. βœ… Refactor UIModule to delegate to EditorManager and ToolbarController
  4. βœ… Update integration tests

Success Criteria:


Phase 3: Comments & Polish (Week 3, 12-16 hours)

Goals:

Tasks:

  1. βœ… Extract CommentCoordinator
    • Move comment rendering logic
    • Extract comment interaction handlers
    • Implement comment state management
    • Test comment workflows
  2. βœ… Final UIModule refactoring
    • Slim down to pure orchestration (~200-300 lines)
    • Clean up internal state
    • Document public API
  3. βœ… Performance optimization
    • Implement batch DOM updates in DOMRenderer
    • Add debouncing to StateManager saves
    • Optimize comment rendering
  4. βœ… Documentation
    • JSDoc for all public methods
    • Architecture diagrams
    • Migration guide

Success Criteria:


Migration Strategy

Backward Compatibility

Approach: Maintain 100% backward compatibility during refactoring

Strategy:

  1. Facade Pattern: Keep UIModule as facade exposing same public API
  2. Internal Delegation: UIModule delegates to specialized classes
  3. Gradual Migration: Consumers can migrate at their own pace
  4. Deprecation Warnings: Mark old patterns as deprecated (not removed)

Example Migration

Before (Current):

const uiModule = new UIModule(config);
await uiModule.initialize();
uiModule.editElement('element-123');

After (New - but old still works):

// Option 1: Use UIModule facade (no changes needed)
const uiModule = new UIModule(config);
await uiModule.initialize();
uiModule.editElement('element-123'); // Still works!

// Option 2: Use specialized classes directly (advanced)
const editorManager = uiModule.getEditorManager();
await editorManager.openEditor('element-123', content);

Testing Strategy

Unit Tests

Coverage Target: >90% for all new classes

Test Files:

tests/unit/ui/
β”œβ”€β”€ EditorManager.test.ts (30-40 tests)
β”œβ”€β”€ CommentCoordinator.test.ts (25-30 tests)
β”œβ”€β”€ ToolbarController.test.ts (20-25 tests)
β”œβ”€β”€ DOMRenderer.test.ts (30-35 tests)
β”œβ”€β”€ StateManager.test.ts (20-25 tests)
└── UIModule-integration.test.ts (40-50 tests)

Test Categories:

  1. Lifecycle Tests: Initialize, destroy, cleanup
  2. State Management Tests: State changes, persistence, validation
  3. Integration Tests: Class collaboration, event flow
  4. Edge Case Tests: Error handling, boundary conditions
  5. Performance Tests: Batch operations, memory usage

Integration Tests

Focus: Ensure classes work together correctly

Scenarios:

  1. Complete edit workflow (open β†’ edit β†’ save)
  2. Comment lifecycle (add β†’ edit β†’ resolve β†’ delete)
  3. Undo/redo with editor and comments
  4. State persistence across page reloads
  5. Multiple editors/comments simultaneously

E2E Tests

Update Existing Tests: Verify no regressions in user-facing functionality


Risk Assessment & Mitigation

Risks

Risk Probability Impact Mitigation
Breaking changes Low High Maintain facade pattern, comprehensive tests
Performance regression Low Medium Benchmark before/after, optimize batch operations
Incomplete extraction Low Medium Phased approach, incremental validation
Merge conflicts Medium Low Small PRs, frequent commits, feature flag
Increased complexity Low Medium Clear interfaces, good documentation

Mitigation Strategies

  1. Feature Flag: Enable/disable new architecture via config
  2. Incremental Rollout: Deploy phase by phase, monitor each
  3. Rollback Plan: Keep old code path available for quick rollback
  4. Monitoring: Track performance metrics, error rates
  5. Documentation: Comprehensive guides for developers

Success Metrics

Quantitative

Qualitative


Timeline

Optimistic (40 hours)

Realistic (50 hours)

Conservative (60 hours)


Dependencies

Requires:

Blocks:


Post-Decomposition Benefits

Immediate (Weeks 1-4)

Short-term (Months 1-3)

Long-term (Months 3-12)


Next Steps

  1. Approval: Review and approve this plan
  2. Branch: Create feature branch refactor/ui-module-decomposition
  3. Phase 1: Begin foundation work (StateManager + DOMRenderer)
  4. Review: PR after each phase for incremental validation
  5. Monitor: Track metrics, gather feedback
  6. Iterate: Adjust approach based on learnings

References


Status: πŸ“‹ Awaiting Approval Owner: Development Team Estimated Start: Immediately after approval Estimated Completion: 3-4 weeks (40-60 hours)