Files
OpenNoodl/dev-docs/tasks/phase-2/TASK-004B-componentsPanel-react-migration/README.md
2025-12-23 09:39:33 +01:00

14 KiB

TASK-005: ComponentsPanel React Migration

⚠️ CURRENT STATUS: BLOCKED

Last Updated: December 22, 2025
Status: 🚫 BLOCKED - Webpack/Electron caching preventing testing
Completion: ~85% (Backend works, UI update blocked)

📖 See STATUS-BLOCKED.md for complete details

Quick Summary

  • Backend rename functionality works perfectly
  • Code fixes implemented correctly in source files
  • Webpack 5 persistent caching prevents new code from loading
  • UI doesn't update after rename because useEventListener never subscribes

Next Action: Requires dedicated investigation into webpack caching issue or alternative approach. See STATUS-BLOCKED.md for detailed analysis and potential solutions.


Overview

Migrate the ComponentsPanel from the legacy jQuery/underscore.js View pattern to a modern React component. This eliminates tech debt, enables the migration badges/filters feature from TASK-004, and establishes a clean pattern for migrating remaining legacy panels.

Phase: 2 (Runtime Migration System)
Priority: HIGH (blocks TASK-004 parts 2 & 3)
Effort: 6-8 hours (Original estimate - actual time ~12 hours due to caching issues)
Risk: Medium → HIGH (Webpack caching complications)


Background

Current State

ComponentsPanel.ts is a ~800 line legacy View class that uses:

  • jQuery for DOM manipulation and event handling
  • Underscore.js HTML templates (componentspanel.html) with data-* attribute bindings
  • Manual DOM updates via scheduleRender() pattern
  • Complex drag-and-drop via PopupLayer integration
  • Deep integration with ProjectModel, NodeGraphEditor, and sheets system

Why Migrate Now?

  1. Blocks TASK-004: Adding migration status badges and filters to a jQuery template creates a Frankenstein component mixing React dialogs into jQuery views
  2. Philosophy alignment: "When we touch a component, we clean it properly"
  3. Pattern establishment: This migration creates a template for other legacy panels
  4. Maintainability: React components are easier to test, extend, and debug

Prior Art

Several patterns already exist in the codebase:

  • ReactView wrapper class for hybrid components
  • SidePanel.tsx - the container that hosts sidebar panels (already React)
  • SidebarModel registration pattern supports both legacy Views and React components
  • UndoQueuePanel example in docs/sidebar.md shows the migration pattern

Goals

  1. Full React rewrite of ComponentsPanel with zero jQuery dependencies
  2. Feature parity with existing functionality (drag-drop, folders, context menus, rename-in-place)
  3. Clean integration with existing SidebarModel registration
  4. Prepare for badges/filters - structure component to easily add TASK-004 features
  5. TypeScript throughout - proper typing, no TSFixme

Architecture

Component Structure

ComponentsPanel/
├── ComponentsPanel.tsx          # Main container, registered with SidebarModel
├── ComponentsPanel.module.scss  # Scoped styles
├── components/
│   ├── ComponentTree.tsx        # Recursive tree renderer
│   ├── ComponentItem.tsx        # Single component row
│   ├── FolderItem.tsx           # Folder row with expand/collapse
│   ├── SheetSelector.tsx        # Sheet tabs (if showSheetList option)
│   └── AddComponentMenu.tsx     # "+" button dropdown
├── hooks/
│   ├── useComponentsPanel.ts    # Main state management hook
│   ├── useDragDrop.ts           # Drag-drop logic
│   └── useRenameMode.ts         # Inline rename handling
├── types.ts                     # TypeScript interfaces
└── index.ts                     # Exports

State Management

Use React hooks with ProjectModel as source of truth:

  • useModernModel hook to subscribe to ProjectModel events
  • Local state for UI concerns (expanded folders, selection, rename mode)
  • Derive tree structure from ProjectModel on each render

Drag-Drop Strategy

Two options to evaluate:

Option A: Native HTML5 Drag-Drop

  • Lighter weight, no dependencies
  • Already used elsewhere in codebase via PopupLayer
  • Requires manual drop zone management

Option B: @dnd-kit library

  • Already planned as dependency for DASH-003 (Project Organisation)
  • Better accessibility, smoother animations
  • More code but cleaner abstractions

Recommendation: Start with Option A to maintain existing PopupLayer integration patterns. Can upgrade to dnd-kit later if needed.


Implementation Phases

Phase 1: Foundation (1-2 hours)

Create the component structure and basic rendering without interactivity.

Files to create:

  • ComponentsPanel.tsx - Shell component
  • ComponentsPanel.module.scss - Base styles (port from existing CSS)
  • types.ts - TypeScript interfaces
  • hooks/useComponentsPanel.ts - State hook skeleton

Tasks:

  1. Create directory structure
  2. Define TypeScript interfaces for component/folder items
  3. Create basic ComponentsPanel that renders static tree
  4. Register with SidebarModel (replacing legacy panel)
  5. Verify it mounts without errors

Success criteria:

  • Panel appears in sidebar
  • Shows hardcoded component list
  • No console errors

Phase 2: Tree Rendering (1-2 hours)

Implement proper tree structure from ProjectModel.

Files to create:

  • components/ComponentTree.tsx
  • components/ComponentItem.tsx
  • components/FolderItem.tsx

Tasks:

  1. Subscribe to ProjectModel with useModernModel
  2. Build folder/component tree structure (port logic from addComponentToFolderStructure)
  3. Implement recursive tree rendering
  4. Add expand/collapse for folders
  5. Implement component selection
  6. Add proper icons (home, page, cloud function, visual)

Success criteria:

  • Tree matches current panel exactly
  • Folders expand/collapse
  • Selection highlights correctly
  • Icons display correctly

Phase 3: Context Menus (1 hour)

Port context menu functionality.

Files to create:

  • components/AddComponentMenu.tsx

Tasks:

  1. Implement header "+" button menu using existing PopupMenu
  2. Implement component right-click context menu
  3. Implement folder right-click context menu
  4. Wire up all actions (rename, duplicate, delete, make home)

Success criteria:

  • All context menu items work
  • Actions perform correctly (components created, renamed, deleted)
  • Undo/redo works for all actions

Phase 4: Drag-Drop (2 hours)

Port the drag-drop system.

Files to create:

  • hooks/useDragDrop.ts

Tasks:

  1. Create drag-drop hook using PopupLayer.startDragging pattern
  2. Implement drag initiation on component/folder rows
  3. Implement drop zones on folders and between items
  4. Port drop validation logic (getAcceptableDropType)
  5. Port drop execution logic (dropOn)
  6. Handle cross-sheet drops

Success criteria:

  • Components can be dragged to folders
  • Folders can be dragged to folders
  • Invalid drops show appropriate feedback
  • Drop creates undo action

Phase 5: Inline Rename (1 hour)

Port rename-in-place functionality.

Files to create:

  • hooks/useRenameMode.ts

Tasks:

  1. Create rename mode state management
  2. Implement inline input field rendering
  3. Handle Enter to confirm, Escape to cancel
  4. Validate name uniqueness
  5. Handle focus management

Success criteria:

  • Double-click or menu triggers rename
  • Input shows with current name selected
  • Enter saves, Escape cancels
  • Invalid names show error

Phase 6: Sheet Selector (30 min)

Port sheet/tab functionality (if showSheetList option is true).

Files to create:

  • components/SheetSelector.tsx

Tasks:

  1. Render sheet tabs
  2. Handle sheet switching
  3. Respect hideSheets option

Success criteria:

  • Sheets display correctly
  • Switching sheets filters component list
  • Hidden sheets don't appear

Phase 7: Polish & Integration (1 hour)

Final cleanup and TASK-004 preparation.

Tasks:

  1. Remove old ComponentsPanel.ts and template
  2. Update any imports/references
  3. Add data attributes for testing
  4. Prepare component structure for badges/filters (TASK-004)
  5. Write migration notes for other legacy panels

Success criteria:

  • No references to old files
  • All tests pass
  • Ready for TASK-004 badge implementation

Files to Modify

Create (New)

packages/noodl-editor/src/editor/src/views/panels/ComponentsPanel/
├── ComponentsPanel.tsx
├── ComponentsPanel.module.scss
├── components/
│   ├── ComponentTree.tsx
│   ├── ComponentItem.tsx
│   ├── FolderItem.tsx
│   ├── SheetSelector.tsx
│   └── AddComponentMenu.tsx
├── hooks/
│   ├── useComponentsPanel.ts
│   ├── useDragDrop.ts
│   └── useRenameMode.ts
├── types.ts
└── index.ts

Modify

packages/noodl-editor/src/editor/src/router.setup.ts
  - Update ComponentsPanel import to new location
  - Verify SidebarModel.register call works with React component

packages/noodl-editor/src/editor/src/models/sidebar/sidebarmodel.tsx
  - May need adjustment if React components need different handling

Delete (After Migration Complete)

packages/noodl-editor/src/editor/src/views/panels/componentspanel/ComponentsPanel.ts
packages/noodl-editor/src/editor/src/templates/componentspanel.html

Keep (Reference/Integration)

packages/noodl-editor/src/editor/src/views/panels/componentspanel/ComponentsPanelFolder.ts
  - Data structure class, can be reused or ported to types.ts

packages/noodl-editor/src/editor/src/views/panels/componentspanel/ComponentTemplates.ts
  - Template definitions, used by AddComponentMenu

Technical Notes

SidebarModel Registration

Current registration in router.setup.ts:

SidebarModel.instance.register({
  id: 'components',
  name: 'Components',
  order: 1,
  icon: IconName.Components,
  onOpen: () => {
    /* ... */
  },
  panelProps: {
    options: {
      showSheetList: true,
      hideSheets: ['__cloud__']
    }
  },
  panel: ComponentsPanel // Currently legacy View class
});

React components can be registered directly - see how SidePanel.tsx handles this with SidebarModel.instance.getPanelComponent().

ProjectModel Integration

Key events to subscribe to:

  • componentAdded - New component created
  • componentRemoved - Component deleted
  • componentRenamed - Component name changed
  • rootComponentChanged - Home component changed

Use useModernModel(ProjectModel.instance, [...events]) pattern.

Existing Patterns to Follow

Look at these files for patterns:

  • SearchPanel.tsx - Modern React sidebar panel
  • VersionControlPanel.tsx - Another React sidebar panel
  • useModernModel hook - Model subscription pattern
  • PopupMenu component - For context menus

CSS Migration

Port styles from:

  • packages/noodl-editor/src/editor/src/styles/componentspanel.css

To CSS modules in ComponentsPanel.module.scss.


Testing Checklist

Basic Rendering

  • Panel appears in sidebar when Components icon clicked
  • Components display with correct names
  • Folders display with correct names
  • Nested structure renders correctly
  • Icons display correctly (home, page, cloud, visual, folder)

Selection

  • Clicking component selects it
  • Clicking folder selects it
  • Selection opens component in node graph editor
  • Only one item selected at a time

Folders

  • Clicking caret expands/collapses folder
  • Folder state persists during session
  • Empty folders display correctly
  • "Folder components" (folders that are also components) work

Context Menus

  • "+" button shows add menu
  • Component context menu shows all options
  • Folder context menu shows all options
  • "Make home" option works
  • "Rename" option works
  • "Duplicate" option works
  • "Delete" option works (with confirmation)

Drag-Drop

  • Can drag component to folder
  • Can drag folder to folder
  • Cannot drag folder into its own children
  • Drop indicator shows correctly
  • Invalid drops show feedback
  • Undo works after drop

Rename

  • Double-click enables rename
  • Context menu "Rename" enables rename
  • Enter confirms rename
  • Escape cancels rename
  • Tab moves to next item (optional)
  • Invalid names show error

Sheets

  • Sheet tabs display (if enabled)
  • Clicking sheet filters component list
  • Hidden sheets don't appear

Integration

  • Warnings icon appears for components with warnings
  • Selection syncs with node graph editor
  • New component appears immediately after creation
  • Deleted component disappears immediately

Risks & Mitigations

Risk: Drag-drop edge cases

Mitigation: Port logic directly from existing implementation, test thoroughly

Risk: Performance with large component trees

Mitigation: Use React.memo for tree items, virtualize if needed (future)

Risk: Breaking existing functionality

Mitigation: Test all features before removing old code, keep old files until verified

Risk: Subtle event timing issues

Mitigation: Use same ProjectModel subscription pattern as other panels


Success Criteria

  1. Feature parity: All existing functionality works identically
  2. No regressions: Existing projects work correctly
  3. Clean code: No jQuery, no TSFixme, proper TypeScript
  4. Ready for TASK-004: Easy to add migration badges/filters
  5. Pattern established: Can be used as template for other panel migrations

Future Enhancements (Out of Scope)

  • Virtualized rendering for huge component trees
  • Keyboard navigation (arrow keys)
  • Multi-select for bulk operations
  • Search/filter within panel (separate from SearchPanel)
  • Drag to reorder (not just move to folder)

Dependencies

Blocked by: None

Blocks:

  • TASK-004 Parts 2 & 3 (Migration Status Badges & Filters)

References

  • Current implementation: views/panels/componentspanel/ComponentsPanel.ts
  • Template: templates/componentspanel.html
  • Styles: styles/componentspanel.css
  • Folder model: views/panels/componentspanel/ComponentsPanelFolder.ts
  • Sidebar docs: packages/noodl-editor/docs/sidebar.md
  • SidePanel container: views/SidePanel/SidePanel.tsx