TASK-008: ComponentsPanel Menu Enhancements & Sheet System
✅ CURRENT STATUS: COMPLETE
Last Updated: January 3, 2026
Status: ✅ COMPLETE
Completion: 100%
Quick Summary
All ComponentsPanel features successfully implemented and working:
- ✅ Enhanced context menus with "Create" submenus - COMPLETE
- ✅ Sheet system backend (detection, filtering, management) - COMPLETE
- ✅ Sheet selector UI with dropdown - COMPLETE
- ✅ Sheet management actions wired up - COMPLETE
Predecessor: TASK-004B (ComponentsPanel React Migration) - COMPLETE ✅
Completed Phases
Phase 1: Enhanced Context Menus ✅
- Create menu items in component/folder right-click menus
- All component templates + folder creation accessible
Phase 2: Sheet System Backend ✅ (December 27, 2025)
- Sheet detection from
#-prefixed folders useComponentsPanelnow exports:sheets,currentSheet,selectSheet- Tree filtering by selected sheet
useSheetManagementhook with full CRUD operations- All operations with undo support
Phase 3: Sheet Selector UI ✅ (January 3, 2026)
- Sheet dropdown component with modern design
- Sheet list with selection indicator
- Three-dot menu for rename/delete actions
- Smooth animations and proper z-index layering
Phase 4: Sheet Management Actions ✅ (January 3, 2026)
- Create sheet with validation and undo support
- Rename sheet with component path updates
- Delete sheet with confirmation dialog
- All operations integrated with UndoQueue
TASK-008C: Drag-Drop System ✅
- All 7 drop combinations working
- Root drop zone implemented
Overview
TASK-004B successfully migrated the ComponentsPanel to React, but several features from the legacy implementation were intentionally deferred. This task completes the ComponentsPanel by adding:
- Enhanced Context Menus: Add "Create" submenus to component and folder right-click menus
- Sheet System UI: Implement dropdown selector for managing component sheets
- Sheet Management: Full CRUD operations for sheets with undo support
Phase: 2 (Runtime Migration System)
Priority: MEDIUM (UX enhancement, not blocking)
Effort: 8-12 hours
Risk: Low (foundation already stable)
Background
What Are Sheets?
Sheets are a way to organize components into top-level groups:
- Sheet Folders: Top-level folders with names starting with
#(e.g.,#CloudFunctions,#Pages) - Default Sheet: All components not in a
#folder - Special Sheets: Some sheets can be hidden (e.g.,
__cloud__sheet)
Current State
After TASK-004B completion, the React ComponentsPanel has:
✅ Working:
- Basic tree rendering with folders/components
- Component selection and navigation
- Expand/collapse folders
- Basic context menus (Make Home, Rename, Duplicate, Delete)
- Drag-drop for organizing components
- Root folder transparency (no unnamed folder)
❌ Missing:
- "Create" submenus in context menus
- Sheet selector UI (currently no way to see/switch sheets)
- Sheet creation/deletion/rename
- Visual indication of current sheet
Legacy Implementation
The legacy ComponentsPanel.ts.legacy shows:
- Full context menu system with "Create" submenus
- Sheet selector bar with tabs
- Sheet management actions (add, rename, delete)
- Sheet drag-drop support
Goals
- Enhanced Context Menus - Add "Create" submenus with all component types + folder
- Sheet Dropdown UI - Replace legacy tab bar with modern dropdown selector
- Sheet Management - Full create/rename/delete with undo support
- Sheet Filtering - Show only components in selected sheet
- TypeScript Throughout - Proper typing, no TSFixme
Architecture
Component Structure
ComponentsPanel/
├── ComponentsPanelReact.tsx # Add sheet selector UI
├── components/
│ ├── ComponentTree.tsx # Enhance context menus
│ ├── ComponentItem.tsx # Update menu items
│ ├── FolderItem.tsx # Update menu items
│ └── SheetSelector.tsx # NEW: Dropdown for sheets
├── hooks/
│ ├── useComponentsPanel.ts # Add sheet filtering
│ ├── useComponentActions.ts # Add sheet actions
│ └── useSheetManagement.ts # NEW: Sheet operations
└── types.ts # Add sheet types
State Management
Sheet State (in useComponentsPanel):
currentSheet: ComponentsPanelFolder | null- Active sheetsheets: ComponentsPanelFolder[]- All available sheetsselectSheet(sheet)- Switch to a sheetfilterBySheet(sheet)- Filter tree to show only sheet components
Sheet Actions (in useSheetManagement):
createSheet(name)- Create new sheet with undorenameSheet(sheet, newName)- Rename sheet with undodeleteSheet(sheet)- Delete sheet with confirmation + undomoveToSheet(item, targetSheet)- Move component/folder to sheet
Implementation Phases
Phase 1: Enhanced Context Menus (2-3 hours)
Add "Create" submenus to existing context menus.
Files to Modify:
components/ComponentItem.tsx- Add "Create" submenu before dividercomponents/FolderItem.tsx- Add "Create" submenu before dividerhooks/useComponentActions.ts- Already hashandleAddComponentandhandleAddFolder
Tasks:
-
Check PopupMenu Submenu Support
- Read PopupMenu source to see if nested menus are supported
- If not, may need to enhance PopupMenu or use alternative approach
-
Add "Create" Submenu to Component Context Menu
- Position: After "Make Home", before "Rename"
- Items:
- Page (template)
- Visual Component (template)
- Logic Component (template)
- Cloud Function (template)
- Divider
- Folder
- Each item calls
handleAddComponent(template, parentPath)
-
Add "Create" Submenu to Folder Context Menu
- Same items as component menu
- Parent path is folder path
-
Wire Up Template Selection
- Get templates from
ComponentTemplates.instance.getTemplates() - Filter by runtime type (browser vs cloud)
- Pass correct parent path to popup
- Get templates from
Success Criteria:
- Component right-click shows "Create" submenu
- Folder right-click shows "Create" submenu
- All 4 component templates + folder appear in submenu
- Clicking template opens creation popup at correct path
- All operations support undo/redo
Phase 2: Sheet System Backend (2 hours)
Implement sheet detection and filtering logic.
Files to Create:
hooks/useSheetManagement.ts- Sheet operations hook
Files to Modify:
hooks/useComponentsPanel.ts- Add sheet filtering
Tasks:
-
Sheet Detection in useComponentsPanel
// Identify sheets from projectFolder.folders const sheets = useMemo(() => { const allSheets = [{ name: 'Default', folder: projectFolder, isDefault: true }]; projectFolder.folders .filter((f) => f.name.startsWith('#')) .forEach((f) => { allSheets.push({ name: f.name.substring(1), // Remove # prefix folder: f, isDefault: false }); }); // Filter out hidden sheets return allSheets.filter((s) => !hideSheets?.includes(s.name)); }, [projectFolder, hideSheets]); -
Current Sheet State
const [currentSheet, setCurrentSheet] = useState(() => { // Default to first non-hidden sheet return sheets[0] || null; }); -
Sheet Filtering
const filteredTreeData = useMemo(() => { if (!currentSheet) return treeData; if (currentSheet.isDefault) { // Show components not in any # folder return filterNonSheetComponents(treeData); } else { // Show only components in this sheet's folder return filterSheetComponents(treeData, currentSheet.folder); } }, [treeData, currentSheet]); -
Create useSheetManagement Hook
createSheet(name)- Create#SheetNamefolderrenameSheet(sheet, newName)- Rename folder with component path updatesdeleteSheet(sheet)- Delete folder and all components (with confirmation)- All operations use
UndoQueue.pushAndDo()pattern
Success Criteria:
- Sheets correctly identified from folder structure
- Current sheet state maintained
- Tree data filtered by selected sheet
- Sheet CRUD operations with undo support
Phase 3: Sheet Selector UI (2-3 hours)
Create dropdown component for sheet selection.
Files to Create:
components/SheetSelector.tsx- Dropdown componentcomponents/SheetSelector.module.scss- Styles
Component Structure:
interface SheetSelectorProps {
sheets: Sheet[];
currentSheet: Sheet | null;
onSelectSheet: (sheet: Sheet) => void;
onCreateSheet: () => void;
onRenameSheet: (sheet: Sheet) => void;
onDeleteSheet: (sheet: Sheet) => void;
}
export function SheetSelector({
sheets,
currentSheet,
onSelectSheet,
onCreateSheet,
onRenameSheet,
onDeleteSheet
}: SheetSelectorProps) {
// Dropdown implementation
}
UI Design:
┌─────────────────────────────────┐
│ Components ▼ [Default] │ ← Header with dropdown
├─────────────────────────────────┤
│ Click dropdown: │
│ ┌─────────────────────────────┐ │
│ │ ● Default │ │
│ │ Pages │ │
│ │ Components │ │
│ │ ──────────────── │ │
│ │ + Add Sheet │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘
Tasks:
-
Create SheetSelector Component
- Button showing current sheet name with dropdown icon
- Click opens dropdown menu
- List of all sheets with selection indicator
- "Add Sheet" button at bottom
-
Sheet List Item with Actions
- Sheet name
- Three-dot menu for rename/delete
- Cannot delete "Default" sheet
- Click sheet name to switch
-
Integrate into ComponentsPanelReact
<div className={css['Header']}> <span className={css['Title']}>Components</span> <SheetSelector sheets={sheets} currentSheet={currentSheet} onSelectSheet={selectSheet} onCreateSheet={handleCreateSheet} onRenameSheet={handleRenameSheet} onDeleteSheet={handleDeleteSheet} /> <button className={css['AddButton']} onClick={handleAddClick}> + </button> </div> -
Style the Dropdown
- Match existing ComponentsPanel styling
- Smooth open/close animation
- Proper z-index layering
Success Criteria:
- Dropdown button shows current sheet name
- Clicking opens sheet list
- Sheet list shows all non-hidden sheets
- "Add Sheet" button at bottom
- Three-dot menu on each sheet (except Default)
- Clicking sheet switches view
Phase 4: Sheet Management Actions (1-2 hours)
Wire up all sheet management actions.
Files to Modify:
ComponentsPanelReact.tsx- Wire up SheetSelector callbacks
Tasks:
-
Create Sheet Action
const handleCreateSheet = useCallback(() => { const popup = new PopupLayer.StringInputPopup({ label: 'New sheet name', okLabel: 'Create', cancelLabel: 'Cancel', onOk: (name: string) => { if (!name || name.trim() === '') { ToastLayer.showError('Sheet name cannot be empty'); return; } createSheet(name); PopupLayer.instance.hidePopup(); } }); popup.render(); PopupLayer.instance.showPopup({ content: popup, position: 'center' }); }, [createSheet]); -
Rename Sheet Action
- Show StringInputPopup with current name
- Validate name (non-empty, unique)
- Call
renameSheet()from useSheetManagement - Update displays new name immediately (via ProjectModel events)
-
Delete Sheet Action
- Show confirmation dialog with component count
- Call
deleteSheet()from useSheetManagement - Switch to Default sheet after deletion
-
Drag-Drop Between Sheets (Optional Enhancement)
- Extend useDragDrop to support sheet boundaries
- Allow dropping on sheet name in dropdown
- Move component/folder to target sheet
Success Criteria:
- "Add Sheet" creates new sheet with undo
- Rename sheet updates all component paths
- Delete sheet removes folder and components
- All operations show in undo history
- UI updates immediately after operations
Phase 5: Integration & Testing (1 hour)
Final integration and comprehensive testing.
Tasks:
-
Update TASK-004B Documentation
- Mark as "Feature Complete" (not just "Complete")
- Add reference to TASK-008 for sheet system
-
Test All Menu Features
- Component context menu "Create" submenu works
- Folder context menu "Create" submenu works
- All templates create components at correct path
- Folder creation from context menu works
-
Test All Sheet Features
- Sheet dropdown displays correctly
- Switching sheets filters component list
- Creating sheet adds to dropdown
- Renaming sheet updates dropdown and paths
- Deleting sheet removes from dropdown
-
Test Edge Cases
- Hidden sheets don't appear in dropdown
- Locked sheet mode prevents switching (for Cloud Functions panel)
- Empty sheets show correctly
- Deleting last component in sheet folder
-
Test Undo/Redo
- Create sheet → undo removes it
- Rename sheet → undo reverts name
- Delete sheet → undo restores it
- Move to sheet → undo moves back
Success Criteria:
- All features working end-to-end
- No console errors or warnings
- Smooth UX with proper feedback
- Documentation updated
Technical Details
PopupMenu Submenu Support
The legacy implementation used nested PopupMenu items. Need to verify if current PopupMenu supports this:
Option A: Nested Menu Support
{
icon: IconName.Plus,
label: 'Create',
submenu: [
{ icon: IconName.Page, label: 'Page', onClick: ... },
{ icon: IconName.Component, label: 'Visual Component', onClick: ... },
// etc
]
}
Option B: Flat Menu with Dividers
[
{ icon: IconName.Plus, label: 'Create Page', onClick: ... },
{ icon: IconName.Plus, label: 'Create Visual Component', onClick: ... },
{ icon: IconName.Plus, label: 'Create Logic Component', onClick: ... },
{ icon: IconName.Plus, label: 'Create Cloud Function', onClick: ... },
{ type: 'divider' },
{ icon: IconName.Plus, label: 'Create Folder', onClick: ... },
{ type: 'divider' },
// existing items...
]
Decision: Check PopupMenu implementation first. If nested menus aren't supported, use Option B as it's simpler and still provides good UX.
Sheet Folder Structure
Sheets are implemented as top-level folders:
projectFolder (root)
├── #Pages/ ← Sheet: "Pages"
│ ├── HomePage
│ ├── AboutPage
├── #Components/ ← Sheet: "Components"
│ ├── Header
│ ├── Footer
├── #__cloud__/ ← Special hidden sheet
│ ├── MyCloudFunction
├── App ← Default sheet
├── Settings ← Default sheet
Key Points:
- Sheet names start with
#in folder structure - Display names remove the
#prefix - Default sheet = any component not in a
#folder - Hidden sheets filtered by
hideSheetsoption
Sheet Filtering Algorithm
function filterBySheet(components, sheet) {
if (sheet.isDefault) {
// Show only components NOT in any sheet folder
return components.filter((comp) => !comp.name.startsWith('/#'));
} else {
// Show only components in this sheet's folder
const sheetPath = sheet.folder.getPath();
return components.filter((comp) => comp.name.startsWith(sheetPath));
}
}
UndoQueue Pattern
All sheet operations must use the proven pattern:
UndoQueue.instance.pushAndDo(
new UndoActionGroup({
label: 'create sheet',
do: () => {
// Perform action
},
undo: () => {
// Revert action
}
})
);
NOT the old broken pattern:
// ❌ DON'T DO THIS
const undoGroup = new UndoActionGroup({ label: 'action' });
undoGroup.push({ do: ..., undo: ... });
UndoQueue.instance.push(undoGroup);
undoGroup.do();
Files to Modify
Create (New)
packages/noodl-editor/src/editor/src/views/panels/ComponentsPanelNew/
├── components/
│ ├── SheetSelector.tsx # NEW
│ └── SheetSelector.module.scss # NEW
└── hooks/
└── useSheetManagement.ts # NEW
Modify (Existing)
packages/noodl-editor/src/editor/src/views/panels/ComponentsPanelNew/
├── ComponentsPanelReact.tsx # Add SheetSelector
├── components/
│ ├── ComponentItem.tsx # Enhance context menu
│ └── FolderItem.tsx # Enhance context menu
├── hooks/
│ ├── useComponentsPanel.ts # Add sheet filtering
│ └── useComponentActions.ts # Add sheet actions
└── types.ts # Add Sheet types
Testing Checklist
Context Menu Enhancements
- Component right-click shows "Create" submenu
- "Create" submenu shows all 4 templates + folder
- Clicking template opens creation popup
- Component created at correct path
- Folder creation works from context menu
- Folder right-click has same "Create" submenu
- All operations support undo/redo
Sheet Selector UI
- Dropdown button appears in header
- Dropdown shows current sheet name
- Clicking opens sheet list
- All non-hidden sheets appear in list
- Current sheet has selection indicator
- "Add Sheet" button at bottom
- Three-dot menu on non-default sheets
- Clicking sheet switches view
Sheet Management
- Create sheet opens input popup
- New sheet appears in dropdown
- Components filtered by selected sheet
- Rename sheet updates name everywhere
- Rename sheet updates component paths
- Delete sheet shows confirmation
- Delete sheet removes from dropdown
- Delete sheet removes all components
- Hidden sheets don't appear (e.g., cloud)
Sheet Filtering
- Default sheet shows non-sheet components
- Named sheet shows only its components
- Switching sheets updates tree immediately
- Empty sheets show empty state
- Component creation adds to current sheet
Undo/Redo
- Create sheet → undo removes it
- Create sheet → undo → redo restores it
- Rename sheet → undo reverts name
- Delete sheet → undo restores sheet and components
- Move to sheet → undo moves back
Edge Cases
- Cannot delete Default sheet
- Cannot create sheet with empty name
- Cannot create sheet with duplicate name
- Locked sheet mode prevents switching
- Hidden sheets stay hidden
- Deleting last component doesn't break UI
Risks & Mitigations
Risk: PopupMenu doesn't support nested menus
Mitigation: Use flat menu structure with dividers. Still provides good UX.
Risk: Sheet filtering breaks component selection
Mitigation: Test extensively. Ensure ProjectModel events update sheet view correctly.
Risk: Sheet delete is destructive
Mitigation: Show confirmation with component count. Make undo work perfectly.
Success Criteria
- Context Menus Enhanced: "Create" submenus with all templates work perfectly
- Sheet UI Complete: Dropdown selector with all management features
- Sheet Operations: Full CRUD with undo support
- Feature Parity: All legacy sheet features now in React
- Clean Code: TypeScript throughout, no TSFixme
- Documentation: Updated task status, learnings captured
Future Enhancements (Out of Scope)
- Drag-drop between sheets (drag component onto sheet name)
- Sheet reordering
- Sheet color coding
- Sheet icons
- Keyboard shortcuts for sheet switching
- Sheet search/filter
Dependencies
Blocked by: None (TASK-004B complete)
Blocks: None (UX enhancement)
References
- TASK-004B: ComponentsPanel React Migration (predecessor)
- Legacy Implementation:
ComponentsPanel.ts.legacy- Complete reference - Current React:
ComponentsPanelReact.tsx- Foundation to build on - Templates:
ComponentTemplates.ts- Template system - Actions:
useComponentActions.ts- Action patterns - Undo Pattern:
dev-docs/reference/UNDO-QUEUE-PATTERNS.md
Notes for Implementation
PopupMenu Investigation
Before starting Phase 1, check:
- Does PopupMenu support nested menus?
- If yes, what's the API?
- If no, is it easy to add or should we use flat menus?
File to check: packages/noodl-editor/src/editor/src/views/PopupLayer/PopupMenu.tsx
Sheet State Management
Consider using a custom hook useSheetState() to encapsulate:
- Current sheet selection
- Sheet list with filtering
- Sheet switching logic
- Persistence (if needed)
This keeps ComponentsPanelReact clean and focused.
Component Path Updates
When renaming sheets, ALL components in that sheet need path updates. This is similar to folder rename. Use the same pattern:
const componentsInSheet = ProjectModel.instance.getComponents().filter((c) => c.name.startsWith(oldSheetPath));
componentsInSheet.forEach((comp) => {
const relativePath = comp.name.substring(oldSheetPath.length);
const newName = newSheetPath + relativePath;
ProjectModel.instance.renameComponent(comp, newName);
});
Hidden Sheets
The hideSheets option is important for panels like the Cloud Functions panel. It might show:
hideSheets: ['__cloud__']- Don't show cloud functions in main panel
OR it might be locked to ONLY cloud functions:
lockCurrentSheetName: '__cloud__'- Only show cloud functions
Both patterns should work seamlessly.
Last Updated: December 26, 2025