# VIEW-007: Semantic Layers **View Type:** 🎨 Canvas Overlay (filters what's visible on canvas) ## Overview Split the chaotic canvas into conceptual layers that can be shown/hidden independently. See just the visual tree, just the data nodes, just the logic, or any combination. Reduce cognitive load by focusing on one concern at a time. **Estimate:** 2-3 days **Priority:** MEDIUM **Complexity:** Low **Dependencies:** VIEW-000 (Foundation), PREREQ-003 (Canvas Overlay Pattern) --- ## The Problem A typical complex Noodl canvas has: - Visual components (Groups, Text, Images) forming a hierarchy - Data nodes (Variables, Objects, Arrays) scattered around - Logic nodes (Conditions, Expressions, Switches) everywhere - API nodes (REST, Functions) mixed in - Event nodes (Send/Receive Event) connecting things All of these are shown simultaneously, creating overwhelming visual noise. You can't see the forest for the trees. --- ## The Solution A layer system that: 1. Categorizes all nodes into semantic layers 2. Allows showing/hiding each layer independently 3. Shows connection counts when layers are hidden 4. Maintains connection visibility (optionally) across hidden layers 5. Provides quick presets for common views --- ## User Stories 1. **As a developer understanding layout**, I want to see only visual components so I can understand the DOM structure. 2. **As a developer debugging data flow**, I want to see only data nodes to understand the state model. 3. **As a developer reviewing logic**, I want to see only logic nodes to verify conditional behavior. 4. **As a developer cleaning up**, I want to quickly see how many nodes are in each category. --- ## UI Design ### Layer Control Panel ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Semantic Layers β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ PRESETS β”‚ β”‚ [All] [Visual Only] [Data Only] [Logic Only] [Custom] β”‚ β”‚ β”‚ β”‚ ─────────────────────────────────────────────────────────────── β”‚ β”‚ β”‚ β”‚ LAYERS Nodes Visible β”‚ β”‚ β”‚ β”‚ [πŸ‘] πŸ“¦ Visual Structure 45 β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β”‚ β”‚ Groups, Pages, Text, Images... β”‚ β”‚ β”‚ β”‚ [πŸ‘] πŸ’Ύ Data / State 22 β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘ β”‚ β”‚ Variables, Objects, Arrays... β”‚ β”‚ β”‚ β”‚ [ ] ⚑ Logic / Control 38 β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ β”‚ β”‚ Conditions, Expressions, Switches... (hidden) β”‚ β”‚ β”‚ β”‚ [πŸ‘] 🌐 API / External 8 β–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ β”‚ β”‚ REST, Functions, Cloud... β”‚ β”‚ β”‚ β”‚ [ ] πŸ“‘ Events / Signals 15 β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘ β”‚ β”‚ Send Event, Receive Event... (hidden) β”‚ β”‚ β”‚ β”‚ [πŸ‘] πŸ”§ Navigation 4 β–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ β”‚ β”‚ Page Router, Navigate... β”‚ β”‚ β”‚ β”‚ ─────────────────────────────────────────────────────────────── β”‚ β”‚ β”‚ β”‚ OPTIONS β”‚ β”‚ [βœ“] Show connections to hidden layers (dotted) β”‚ β”‚ [βœ“] Fade hidden layers instead of removing β”‚ β”‚ [ ] Auto-hide empty layers β”‚ β”‚ β”‚ β”‚ ─────────────────────────────────────────────────────────────── β”‚ β”‚ β”‚ β”‚ Currently showing: 79 of 132 nodes (60%) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### Canvas with Hidden Layers When a layer is hidden, nodes can either: 1. **Disappear completely** - Node gone, connections rerouted or hidden 2. **Fade to ghost** - 20% opacity, non-interactive 3. **Collapse to indicator** - Small badge showing "12 hidden logic nodes" Example with "fade" mode: ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ CANVAS β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Page β”‚ ← Full opacity (Visual layer visible) β”‚ β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β” β”Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ” β”‚ β”‚ β”‚ Group β”‚β•Œβ•Œβ•Œβ•Œβ•Œβ”‚ Condition β”‚ ← 20% opacity (Logic hidden) β”‚ β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ”˜ β”‚ β”‚ β”‚ β•Ž β”‚ β”‚ β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β” β•Ž β”‚ β”‚ β”‚ Text β”‚β—„β•Œβ•Œβ•Œβ•Œβ”˜ ← Dotted connection to hidden layer β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Variableβ”‚ ← Full opacity (Data layer visible) β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β”Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ” β”‚ β”‚ β”‚ ⚑ 12 Logic nodes hidden β”‚ ← Collapse indicator β”‚ β”‚ β””β•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ•Œβ”˜ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ### Quick Toggle (Keyboard Shortcuts) ``` Cmd+1 β†’ Toggle Visual layer Cmd+2 β†’ Toggle Data layer Cmd+3 β†’ Toggle Logic layer Cmd+4 β†’ Toggle API layer Cmd+5 β†’ Toggle Events layer Cmd+0 β†’ Show all layers ``` --- ## Technical Design ### Layer Definition ```typescript interface SemanticLayer { id: string; name: string; icon: string; description: string; color: string; // For connection coloring // Which node types belong to this layer nodeTypes: string[]; // Match function for complex cases matchNode?: (node: NodeGraphNode) => boolean; } const SEMANTIC_LAYERS: SemanticLayer[] = [ { id: 'visual', name: 'Visual Structure', icon: 'πŸ“¦', description: 'Groups, Pages, Text, Images...', color: '#4A90D9', nodeTypes: [ 'Group', 'Page', 'Text', 'Image', 'Video', 'Button', 'Checkbox', 'Radio', 'Dropdown', 'TextInput', 'Repeater', 'Columns', 'Circle', 'Rectangle', 'Icon', 'Lottie' // ... all visual node types ] }, { id: 'data', name: 'Data / State', icon: 'πŸ’Ύ', description: 'Variables, Objects, Arrays...', color: '#50C878', nodeTypes: [ 'Variable', 'Object', 'Array', 'String', 'Number', 'Boolean', 'Color', 'Set Variable', 'Create Object', 'Array Filter', 'Array Map', 'Insert Into Array', 'Remove From Array' ] }, { id: 'logic', name: 'Logic / Control', icon: '⚑', description: 'Conditions, Expressions, Switches...', color: '#F5A623', nodeTypes: [ 'Condition', 'Expression', 'Switch', 'And', 'Or', 'Not', 'Inverter', 'Delay', 'Debounce', 'Counter', 'States', 'For Each', 'Run Tasks' ] }, { id: 'api', name: 'API / External', icon: '🌐', description: 'REST, Functions, Cloud...', color: '#BD10E0', nodeTypes: [ 'REST', 'REST Query', 'GraphQL', 'Function', 'Javascript', 'Cloud Function', 'Query Records', 'Create Record', 'Update Record', 'Delete Record' ] }, { id: 'events', name: 'Events / Signals', icon: 'πŸ“‘', description: 'Send Event, Receive Event...', color: '#FF6B6B', nodeTypes: [ 'Send Event', 'Receive Event', 'Component Inputs', 'Component Outputs', 'Page Inputs', 'Page Outputs' ] }, { id: 'navigation', name: 'Navigation', icon: 'πŸ”§', description: 'Page Router, Navigate...', color: '#9B9B9B', nodeTypes: [ 'Page Router', 'Navigate', 'Navigate Back', 'External Link', 'Open Popup', 'Close Popup' ] } ]; ``` ### Layer State ```typescript interface LayerState { layers: { [layerId: string]: { visible: boolean; nodeCount: number; }; }; options: { showHiddenConnections: boolean; // Show dotted lines to hidden nodes fadeHiddenLayers: boolean; // Fade instead of hide autoHideEmpty: boolean; // Hide layers with 0 nodes }; preset: 'all' | 'visual' | 'data' | 'logic' | 'custom'; } // Presets const PRESETS = { all: ['visual', 'data', 'logic', 'api', 'events', 'navigation'], visual: ['visual'], data: ['data'], logic: ['logic', 'data'], // Logic often needs data context custom: null // User-defined }; ``` ### Integration with Canvas ```typescript // In NodeGraphEditor or similar interface LayerFilterOptions { visibleLayers: string[]; fadeHidden: boolean; showHiddenConnections: boolean; } function applyLayerFilter(options: LayerFilterOptions): void { this.nodes.forEach(node => { const layer = getNodeLayer(node.model); const isVisible = options.visibleLayers.includes(layer); if (options.fadeHidden) { node.setOpacity(isVisible ? 1.0 : 0.2); node.setInteractive(isVisible); } else { node.setVisible(isVisible); } }); this.connections.forEach(conn => { const fromVisible = options.visibleLayers.includes(getNodeLayer(conn.from)); const toVisible = options.visibleLayers.includes(getNodeLayer(conn.to)); if (fromVisible && toVisible) { conn.setStyle('solid'); conn.setOpacity(1.0); } else if (options.showHiddenConnections) { conn.setStyle('dotted'); conn.setOpacity(0.3); } else { conn.setVisible(false); } }); this.repaint(); } ``` --- ## Implementation Phases ### Phase 1: Layer Categorization (0.5 day) 1. Define all semantic layers with node type mappings 2. Create `getNodeLayer()` function 3. Handle edge cases (unknown types β†’ "Other") 4. Test categorization accuracy **Verification:** - [ ] All node types categorized - [ ] Categories make sense - [ ] No nodes fall through ### Phase 2: Layer State Management (0.5 day) 1. Create layer state store/context 2. Implement toggle functions 3. Add preset switching 4. Persist state to session/preferences **Verification:** - [ ] Toggle works - [ ] Presets switch correctly - [ ] State persists ### Phase 3: Canvas Integration (1 day) 1. Hook layer state into NodeGraphEditor 2. Implement node visibility/opacity changes 3. Implement connection styling 4. Handle selection in hidden layers 5. Performance optimization **Verification:** - [ ] Nodes show/hide correctly - [ ] Connections styled appropriately - [ ] Selection works - [ ] Performance acceptable ### Phase 4: UI Panel (0.5-1 day) 1. Create `SemanticLayersPanel` component 2. Show layer toggles with counts 3. Add options checkboxes 4. Add preset buttons 5. Integrate into Analysis Panel or as floating control **Verification:** - [ ] Panel renders correctly - [ ] Toggles work - [ ] Counts accurate ### Phase 5: Keyboard Shortcuts & Polish (0.5 day) 1. Add keyboard shortcuts 2. Add visual indicator for active filter 3. Toast notification when layers change 4. Help tooltip explaining layers **Verification:** - [ ] Shortcuts work - [ ] User knows filter is active - [ ] UX polished --- ## Files to Create ``` packages/noodl-editor/src/editor/src/views/ β”œβ”€β”€ SemanticLayersPanel/ β”‚ β”œβ”€β”€ index.ts β”‚ β”œβ”€β”€ SemanticLayersPanel.tsx β”‚ β”œβ”€β”€ SemanticLayersPanel.module.scss β”‚ β”œβ”€β”€ LayerToggle.tsx β”‚ β”œβ”€β”€ PresetButtons.tsx β”‚ └── LayerOptions.tsx └── context/ └── SemanticLayersContext.tsx packages/noodl-editor/src/editor/src/utils/ └── semanticLayers.ts ``` --- ## Success Criteria - [ ] All nodes correctly categorized into layers - [ ] Toggling layers shows/hides nodes - [ ] Connections styled appropriately when crossing layers - [ ] Presets work correctly - [ ] Keyboard shortcuts functional - [ ] Performance acceptable (< 100ms to toggle) - [ ] Clear indication when filter is active --- ## Future Enhancements - **Custom layers** - Let users define their own categorizations - **Layer locking** - Prevent editing nodes in certain layers - **Layer-based minimap** - Color-coded minimap by layer - **Save filter as preset** - Save custom combinations - **Per-component layer memory** - Remember last layer state per component --- ## Risks & Mitigations | Risk | Mitigation | |------|------------| | Some nodes hard to categorize | Add "Other" category, let user recategorize | | Performance with many nodes | Use efficient DOM updates, consider virtualization | | Confusion about hidden nodes | Clear indicator, easy "show all" escape hatch | | Selection in hidden layers | Either prevent or show warning | --- ## Dependencies - VIEW-000 Foundation (for categorization utilities) - Canvas editor integration ## Blocks - None (independent view) ## Runtime Integration (PRESERVE EXISTING) **This view modifies the canvas display, so it MUST preserve the existing runtime debugging features.** ### What Must Keep Working When layers are filtered, the following must still function: | Feature | Behavior with hidden layers | |---------|----------------------------| | **Node highlighting** | Hidden nodes should still flash (at reduced opacity if faded) | | **Data flow animation** | Connections to hidden nodes should animate (dotted style) | | **Debug inspector** | Should still work on faded/hidden nodes | | **Click-to-select in preview** | Should reveal hidden node if clicked | ### Implementation Consideration The layer system should be a **visual filter only**, not a functional filter. The runtime doesn't care about layers - it still executes all nodes. We're just changing what the user sees. ```typescript // WRONG: Don't exclude nodes from runtime nodes.filter(n => isLayerVisible(n)).forEach(n => n.execute()); // RIGHT: Only change visual presentation nodes.forEach(node => { const isVisible = isLayerVisible(node); node.setOpacity(isVisible ? 1.0 : 0.2); // Runtime highlighting still works on faded nodes }); ``` ### Auto-Reveal on Activity Consider: when a hidden node fires (lights up due to runtime activity), should it: 1. **Stay hidden/faded** - Consistent filtering, but user might miss activity 2. **Temporarily reveal** - Shows what's happening, but breaks filter 3. **Pulse indicator** - Small badge shows "activity in hidden layers" Recommend option 3 - shows activity without breaking the filter. --- ## Alternative: Non-Invasive Implementation If modifying the canvas is too risky, an alternative is a **read-only layer view** that: 1. Creates a simplified representation of the canvas 2. Shows only selected layers in this separate view 3. Click to jump to actual canvas location This would be safer but less integrated.