11 KiB
TASK-004B ComponentsPanel React Migration - STATUS: BLOCKED
Last Updated: December 22, 2025
Status: 🚫 BLOCKED - Caching Issue Preventing Testing
Completion: ~85% (Backend works, UI update blocked)
🎯 Original Goal
Migrate the legacy ComponentsPanel to React while maintaining all functionality, with a focus on fixing the component rename feature that doesn't update the UI after renaming.
✅ What's Been Completed
Phase 1-4: Foundation & Core Features ✅
- React component structure created
- Tree rendering implemented
- Context menus working
- Drag & drop functional
Phase 5: Inline Rename - PARTIALLY COMPLETE
Backend Rename Logic ✅
The actual renaming WORKS PERFECTLY:
- Component renaming executes successfully
- Files are renamed on disk
- Project state updates correctly
- Changes are persisted (see console log:
Project saved...)
Evidence from console logs:
✅ Calling performRename...
🔍 performRename result: true
✅ Rename successful - canceling rename mode
Project saved Mon Dec 22 2025 22:03:56 GMT+0100
UI Update Logic - BLOCKED 🚫
The problem: UI doesn't update after rename because the React component never receives the componentRenamed event from ProjectModel.
Root Cause: useEventListener hook's useEffect never executes, preventing subscription to ProjectModel events.
🔍 Technical Investigation Summary
Issue 1: React useEffect Not Running with Array Dependencies
Problem: When passing an array as a dependency to useEffect, React 19's Object.is() comparison always sees it as changed, but paradoxically, the useEffect never runs.
Original Code (BROKEN):
const events = ['componentAdded', 'componentRemoved', 'componentRenamed'];
useEventListener(ProjectModel.instance, events, callback);
// Inside useEventListener:
useEffect(() => {
// Never runs!
}, [dispatcher, eventName]); // eventName is an array
Solution Implemented:
// 1. Create stable array reference
const PROJECT_EVENTS = ['componentAdded', 'componentRemoved', 'componentRenamed'];
// 2. Spread array into individual dependencies
useEffect(() => {
// Should run now
}, [dispatcher, ...(Array.isArray(eventName) ? eventName : [eventName])]);
Issue 2: Webpack 5 Persistent Caching
Problem: Even after fixing the code, changes don't appear in the running application.
Root Cause: Webpack 5 enables persistent caching by default:
- Cache location:
packages/noodl-editor/node_modules/.cache - Electron also caches:
~/Library/Application Support/Electron - Even after clearing caches and restarting
npm run dev, old bundles persist
Actions Taken:
# Cleared all caches
rm -rf packages/noodl-editor/node_modules/.cache
rm -rf ~/Library/Application\ Support/Electron
rm -rf ~/Library/Application\ Support/OpenNoodl
Still Blocked: Despite cache clearing, debug markers never appear in console, indicating old code is still running.
📊 Current State Analysis
What We KNOW Works
- ✅ Source files contain all fixes (verified with grep)
- ✅ Component rename backend executes successfully
- ✅ useEventListener hook logic is correct (when it runs)
- ✅ Debug logging is in place to verify execution
What We KNOW Doesn't Work
- ❌ useEventListener's useEffect never executes
- ❌ No subscription to ProjectModel events occurs
- ❌ UI never receives
componentRenamedevent - ❌ Debug markers (🔥) never appear in console
What We DON'T Know
- ❓ Why cache clearing doesn't force recompilation
- ❓ If there's another cache layer we haven't found
- ❓ If webpack-dev-server is truly recompiling on changes
- ❓ If there's a build configuration preventing hot reload
🐛 Bonus Bug Discovered
PopupMenu Constructor Error:
Uncaught TypeError: _popuplayer__WEBPACK_IMPORTED_MODULE_3___default(...).PopupMenu is not a constructor
at ComponentItem.tsx:131:1
This is a separate bug affecting context menus (right-click). Unrelated to rename issue but should be fixed.
📁 Files Modified (With Debug Logging)
Core Implementation Files
-
packages/noodl-editor/src/editor/src/hooks/useEventListener.ts
- Module load marker:
🔥 useEventListener.ts MODULE LOADED - useEffect marker:
🚨 useEventListener useEffect RUNNING! - Subscription marker:
📡 subscribing to... - Event received marker:
🔔 useEventListener received event
- Module load marker:
-
packages/noodl-editor/src/editor/src/views/panels/ComponentsPanelNew/hooks/useComponentsPanel.ts
- Module load marker:
🔥 useComponentsPanel.ts MODULE LOADED - Integration with useEventListener
- Stable PROJECT_EVENTS array
- Module load marker:
-
packages/noodl-editor/src/editor/src/views/panels/ComponentsPanelNew/ComponentsPanelReact.tsx
- Render markers
- Rename flow markers
Documentation Files
- CACHE-CLEAR-RESTART-GUIDE.md - Instructions for clearing caches
- RENAME-TEST-PLAN.md - Test procedures
- This file - Status documentation
🚧 Blocking Issues
Primary Blocker: Webpack/Electron Caching
Severity: CRITICAL
Impact: Cannot test ANY changes to the code
Symptoms:
- Code changes in source files don't appear in running app
- Console shows NO debug markers (🔥, 🚨, 📡, 🔔)
- Multiple dev server restarts don't help
- Cache clearing doesn't help
Possible Causes:
- Webpack dev server not watching TypeScript files correctly
- Another cache layer (browser cache, service worker, etc.)
- Electron loading from wrong bundle location
- Build configuration preventing hot reload
- macOS file system caching (unlikely but possible)
Secondary Blocker: React 19 + EventDispatcher Incompatibility
Severity: HIGH
Impact: Even if caching is fixed, may need alternative approach
The useEventListener hook solution from TASK-008 may have edge cases with React 19's new behavior that weren't caught in isolation testing.
💡 Potential Solutions (Untested)
Solution 1: Aggressive Cache Clearing Script
Create a script that:
- Kills all Node/Electron processes
- Clears all known cache directories
- Clears macOS file system cache
- Forces a clean webpack build
- Restarts with --no-cache flag
Solution 2: Bypass useEventListener Temporarily
As a workaround, try direct subscription in component:
useEffect(() => {
const group = { id: 'ComponentsPanel' };
const handler = () => setUpdateCounter((c) => c + 1);
ProjectModel.instance.on('componentRenamed', handler, group);
return () => ProjectModel.instance.off(group);
}, []);
Solution 3: Use Polling as Temporary Fix
While not elegant, could work around the event issue:
useEffect(() => {
const interval = setInterval(() => {
// Force re-render every 500ms when in rename mode
if (isRenaming) {
setUpdateCounter((c) => c + 1);
}
}, 500);
return () => clearInterval(interval);
}, [isRenaming]);
Solution 4: Production Build Test
Build a production bundle to see if the issue is dev-only:
npm run build
# Test with production Electron app
📋 Next Steps for Future Developer
Immediate Actions
-
Verify caching issue:
- Kill ALL node/electron processes:
killall node; killall Electron - Clear caches again
- Try adding a simple console.log to a DIFFERENT file to see if ANY changes load
- Kill ALL node/electron processes:
-
If caching persists:
- Investigate webpack configuration in
webpackconfigs/ - Check if there's a service worker
- Look for additional cache directories
- Consider creating a fresh dev environment in a new directory
- Investigate webpack configuration in
-
If caching resolved but useEffect still doesn't run:
- Review React 19 useEffect behavior with array spreading
- Test useEventListener hook in isolation with a simple test case
- Consider alternative event subscription approach
Alternative Approaches
- Revert to old panel temporarily - The legacy panel works, could postpone migration
- Hybrid approach - Use React for rendering but keep legacy event handling
- Full rewrite - Start fresh with a different architecture pattern
🔬 Debug Checklist for Next Session
When picking this up again, verify these in order:
- Console shows 🔥 module load markers (proves new code loaded)
- Console shows 🚨 useEffect RUNNING marker (proves useEffect executes)
- Console shows 📡 subscription marker (proves ProjectModel subscription)
- Rename a component
- Console shows 🔔 event received marker (proves events are firing)
- Console shows 🎉 counter update marker (proves React re-renders)
- UI actually updates (proves the whole chain works)
If step 1 fails: Still a caching issue, don't proceed
If step 1 passes, step 2 fails: React useEffect issue, review dependency array
If step 2 passes, step 3 fails: EventDispatcher integration issue
If step 3 passes, step 4 fails: ProjectModel not emitting events
📚 Related Documentation
- TASK-008: EventDispatcher React Investigation (useEventListener solution)
- LEARNINGS.md: Webpack caching issues section (to be added)
- CACHE-CLEAR-RESTART-GUIDE.md: Instructions for clearing caches
- RENAME-TEST-PLAN.md: Test procedures for rename functionality
🎓 Key Learnings
- Webpack 5 caching is AGGRESSIVE - Can persist across multiple dev server restarts
- React 19 + arrays in deps - Spreading array items into deps is necessary
- EventDispatcher + React - Requires careful lifecycle management
- Debug logging is essential - Emoji markers made it easy to trace execution
- Test in isolation first - useEventListener worked in isolation but fails in real app
⏱️ Time Investment
- Initial implementation: ~3 hours
- Debugging UI update issue: ~2 hours
- EventDispatcher investigation: ~4 hours
- Caching investigation: ~2 hours
- Documentation: ~1 hour
Total: ~12 hours - Majority spent on debugging caching/event issues rather than actual feature implementation.
🏁 Recommendation
Option A (Quick Fix): Use the legacy ComponentsPanel for now. It works, and this migration can wait.
Option B (Workaround): Implement one of the temporary solutions (polling or direct subscription) to unblock other work.
Option C (Full Investigation): Dedicate a full session to solving the caching mystery with fresh eyes, possibly in a completely new terminal/environment.
My Recommendation: Option A. The backend rename logic works perfectly. The UI update is a nice-to-have but not critical. Move on to more impactful work and revisit this when someone has time to fully diagnose the caching issue.