Files
OpenNoodl/dev-docs/tasks/phase-2-react-migration/TASK-008-componentspanel-menus-and-sheets/CHANGELOG.md
2026-01-08 13:27:38 +01:00

7.6 KiB

TASK-008: ComponentsPanel Menus and Sheets - CHANGELOG

Status: COMPLETED

Date Completed: January 3, 2026


Summary

Successfully completed all ComponentsPanel enhancements including context menus, sheet system backend, sheet selector UI, and sheet management actions. This changelog primarily documents a critical useMemo bug fix discovered during final testing, but all planned features from the README were fully implemented and are working correctly.


Implementation Status

All Phases Completed:

  • Phase 1: Enhanced Context Menus - Component and folder right-click menus with "Create" submenus
  • Phase 2: Sheet System Backend - Sheet detection, filtering, and CRUD operations with undo support
  • Phase 3: Sheet Selector UI - Dropdown component with modern design and selection indicators
  • Phase 4: Sheet Management Actions - Full create/rename/delete functionality integrated with UndoQueue

Critical Bug Fix - React useMemo Reference Issue

Fixed inability to edit or delete sheets in the Components Panel dropdown. The issue was caused by React's useMemo not detecting when the sheets array had changed, even though the array was being recalculated correctly. The fix involved adding updateCounter to the useMemo dependencies to force a new array reference creation.


Root Cause

React's useMemo performs reference equality checking (===) on its dependencies. When the sheets array was recalculated, useMemo was creating a new array with the same values but not creating a NEW REFERENCE that React could detect as changed. This caused:

  1. SheetSelector to receive the same array reference on every render
  2. React to skip re-rendering the dropdown because the prop reference hadn't changed
  3. Newly created or deleted sheets to not appear in the UI

The Critical Fix

File: packages/noodl-editor/src/editor/src/views/panels/ComponentsPanelNew/hooks/useComponentsPanel.ts

Line 153: Added updateCounter to sheets useMemo dependencies:

const sheets = useMemo((): Sheet[] => {
  // Sheet calculation logic...
  return result;
}, [rawComponents, allComponents, hideSheets, updateCounter]); // ← Added updateCounter here

Why this works:

  • updateCounter increments whenever a ProjectModel event fires (componentAdded, componentRemoved, etc.)
  • When updateCounter changes, useMemo recalculates AND returns a new array reference
  • React detects the new reference and triggers a re-render of components using sheets
  • SheetSelector dropdown updates to show current sheets

Investigation Process

Initial Symptoms

  • User could not edit or delete sheets from the dropdown menu
  • Creating new sheets worked but they didn't appear in the dropdown
  • Deleting sheets threw "sheet doesn't exist" errors
  • Refreshing/restarting the editor showed the correct sheets

Debugging Journey

  1. Verified event system: Added extensive debug logging to confirm:

    • ComponentAdded events fire correctly
    • ProjectModel.instance receives events
    • useComponentsPanel receives events and increments updateCounter
    • Sheets array recalculates with correct values
    • All sheet placeholders detected properly
  2. React rendering investigation: Logs showed:

    • 🔥 Sheets useMemo recalculated correctly
    • But SheetSelector component didn't re-render
    • sheets prop reference remained the same
  3. The "Aha" Moment: Realized useMemo was calculating a new array but not creating a new reference that React could detect as different. The array had the same shape and values, so React's shallow comparison saw it as unchanged.

  4. Solution: Adding updateCounter to deps forces useMemo to return a completely new array reference whenever events fire, triggering React's re-render cycle.


Files Modified

Core Fix

  • packages/noodl-editor/src/editor/src/views/panels/ComponentsPanelNew/hooks/useComponentsPanel.ts
    • Added updateCounter to sheets useMemo dependencies (line 153)

Debug Cleanup (Removed extensive logging from)

  • packages/noodl-editor/src/editor/src/views/panels/ComponentsPanelNew/hooks/useComponentsPanel.ts
  • packages/noodl-editor/src/editor/src/views/panels/ComponentsPanelNew/ComponentsPanelReact.tsx
  • packages/noodl-editor/src/editor/src/views/panels/ComponentsPanelNew/components/SheetSelector.tsx
  • packages/noodl-editor/src/editor/src/views/panels/ComponentsPanelNew/hooks/useSheetManagement.ts
  • packages/noodl-editor/src/editor/src/models/projectmodel.ts

Testing & Verification

Verification Steps:

  1. Create new sheet → appears immediately in dropdown
  2. Rename sheet → updates immediately in dropdown
  3. Delete sheet → removes immediately from dropdown
  4. Move components between sheets → component counts update
  5. Sheet selector shows all sheets with correct counts
  6. No console errors or warnings

Note: Required npm run clean:all and dev server restart due to Webpack filesystem caching issues during development. See LEARNINGS.md for details on this common development pitfall.


Secondary Issue: Webpack Caching

During development, code changes sometimes didn't load due to Webpack's filesystem cache. This obscured the actual fix for several debugging iterations.

Solution Pattern:

npm run clean:all  # Clear all caches
# Restart dev server
# Check build canary timestamp in console

Prevention: Dev mode webpack config should use memory cache or no cache, not filesystem cache.


Lessons Learned

Critical Pattern: React UseMemo with Arrays

WRONG - Recalculation doesn't guarantee new reference:

const sheets = useMemo((): Sheet[] => {
  // Calculate sheets...
  return result; // Same reference if deps haven't changed
}, [rawComponents, allComponents, hideSheets]);

CORRECT - Force new reference with counter:

const [updateCounter, setUpdateCounter] = useState(0);

// Increment on events
useEffect(() => {
  const handleUpdate = () => setUpdateCounter((c) => c + 1);
  ProjectModel.instance.on(EVENTS, handleUpdate, group);
  return () => ProjectModel.instance.off(group);
}, []);

const sheets = useMemo((): Sheet[] => {
  // Calculate sheets...
  return result; // New reference when updateCounter changes
}, [rawComponents, allComponents, hideSheets, updateCounter]);

Key Insights

  1. useMemo doesn't always return new references: Even when recalculating, if dependencies haven't changed by reference, the same memoized value is returned

  2. Update counters force React updates: A simple incrementing counter in deps guarantees a new reference on every calculation

  3. EventDispatcher + React requires patterns: Direct .on() subscriptions from React components silently fail. Use the proven direct subscription pattern or abstracted hooks

  4. Debug by elimination: When events fire and logic executes correctly but UI doesn't update, suspect React reference equality issues

  5. Webpack cache can obscure fixes: Always verify code changes are actually loaded (npm run clean:all) before spending hours debugging


Documentation Updates

  • Updated dev-docs/reference/LEARNINGS.md with React useMemo reference discovery
  • Existing EventDispatcher + React patterns documented in Phase 0
  • Webpack caching issues already documented in LEARNINGS.md

  • EventDispatcher + React Pattern: dev-docs/patterns/REACT-EVENTDISPATCHER.md
  • Webpack Cache Issues: Section in dev-docs/reference/LEARNINGS.md
  • Phase 0 Foundation: dev-docs/tasks/phase-0-foundation-stabalisation/

Task completed January 3, 2026 by Cline