Finished component sidebar updates, with one small bug remaining and documented

This commit is contained in:
Richard Osborne
2025-12-28 22:07:29 +01:00
parent 5f8ce8d667
commit fad9f1006d
193 changed files with 22245 additions and 506 deletions

View File

@@ -0,0 +1,352 @@
# Canvas Visualization Views - Cline Implementation Guide
## Quick Start
**READ THIS FIRST before starting any implementation work.**
This project adds 7 visualization views to help users navigate complex Noodl projects. The views are divided into three types:
| Type | What It Does | Examples |
|------|--------------|----------|
| 🗺️ **Meta Views** | Replace canvas with project-wide view | Topology Map, Trigger Chain |
| 📋 **Sidebar Panels** | Open alongside canvas | X-Ray, Census |
| 🎨 **Canvas Overlays** | Enhance canvas with persistent highlighting | Layers, Lineage, Impact |
---
## ⚠️ CRITICAL: Prerequisites First!
**DO NOT START VIEW IMPLEMENTATION until prerequisites are complete.**
### Prerequisite Order
```
PREREQ-001: Fix Webpack Caching ──────┐
├──► PREREQ-002 + PREREQ-003 (parallel)
│ │
│ ▼
│ VIEW-000: Foundation
│ │
│ ▼
│ First Views (002, 004)
│ │
│ ▼
└──► PREREQ-004: Canvas Highlighting
Canvas Overlays (005, 006, 007)
Meta Views (001, 003)
```
### PREREQ-001: Webpack 5 Caching Fix (CRITICAL)
**Problem:** Webpack 5 persistent caching prevents code changes from loading during development.
**Location of issue documentation:** `dev-docs/tasks/phase-2/TASK-004B-componentsPanel-react-migration/STATUS-BLOCKED.md`
**Must fix before:** Any development work
**Potential solutions:**
1. Disable persistent caching in dev mode (`cache: false` in webpack config)
2. Configure proper cache invalidation
3. Add cache-busting to development build
### PREREQ-002: React 19 Debug Fixes
**Problem:** Legacy `ReactDOM.render()` calls crash debug infrastructure.
**Files to fix:**
```
nodegrapheditor.debuginspectors.js → Replace ReactDOM.render() with createRoot()
commentlayer.ts → Reuse existing root instead of creating new one
TextStylePicker.jsx → Replace ReactDOM.render() with createRoot()
```
**Pattern:**
```javascript
// Before (broken):
ReactDOM.render(<Component />, container);
ReactDOM.unmountComponentAtNode(container);
// After (correct):
if (!this.root) {
this.root = createRoot(container);
}
this.root.render(<Component />);
// On dispose: this.root.unmount();
```
### PREREQ-003: Document Canvas Overlay Pattern
**Good news:** CommentLayer already works as a React overlay on the canvas!
**Task:** Study and document how CommentLayer works:
1. How it renders React over the canvas
2. How it responds to pan/zoom
3. How it integrates with selection
4. Extract reusable patterns for other overlays
**Key file:** `packages/noodl-editor/src/editor/src/views/nodegrapheditor/commentlayer.ts`
### PREREQ-004: Canvas Highlighting API
**Needed for:** VIEW-005 (Lineage), VIEW-006 (Impact), VIEW-007 (Layers)
**Create API for persistent, multi-channel highlighting:**
```typescript
interface CanvasHighlightAPI {
highlightNodes(nodeIds: string[], options: HighlightOptions): HighlightHandle;
highlightConnections(connections: ConnectionRef[], options: HighlightOptions): HighlightHandle;
highlightPath(path: PathDefinition, options: HighlightOptions): HighlightHandle;
clearChannel(channel: string): void;
clearAll(): void;
}
interface HighlightOptions {
channel: string; // 'lineage', 'impact', 'selection'
color?: string;
style?: 'solid' | 'pulse' | 'glow';
persistent?: boolean; // Stay until dismissed
}
```
**Key behavior:** Highlights persist across component navigation!
---
## Implementation Order
### Phase 1: Foundation (After Prerequisites)
**Start with VIEW-000** - this blocks everything else.
```
VIEW-000: Foundation & Shared Utilities
├── Graph traversal utilities
├── Cross-component resolution
├── View infrastructure (Meta Views, Overlays, Panels)
├── Navigation helpers
└── Debug infrastructure documentation
```
### Phase 2: Quick Wins (Can parallelize)
These are low complexity and don't need canvas overlays:
```
VIEW-002: Component X-Ray (Sidebar Panel)
VIEW-004: Node Census (Sidebar Panel)
```
### Phase 3: Canvas Overlays (After PREREQ-004)
```
VIEW-007: Semantic Layers (Canvas Overlay - simplest)
VIEW-005: Data Lineage (Canvas Overlay + panel)
VIEW-006: Impact Radar (Canvas Overlay + panel)
```
### Phase 4: Meta Views (Most complex)
```
VIEW-001: Project Topology Map (Meta View)
VIEW-003: Trigger Chain Debugger (Meta View - needs runtime integration)
```
---
## Key Architectural Decisions
### 1. Three View Types
**🗺️ Meta Views** replace the canvas entirely:
- Tab in header: `[🗺️ Canvas] [📊 Topology] [⚡ Triggers]`
- Full-screen project-wide or timeline view
- Component panel still exists but works differently
**📋 Sidebar Panels** open alongside canvas:
- Use existing SidebarModel (already React-ready)
- Click items to navigate/highlight on canvas
**🎨 Canvas Overlays** enhance the beloved canvas:
- Toggle buttons in canvas area: `[Layers] [Lineage] [Impact]`
- Based on CommentLayer pattern (already works!)
- Persistent highlighting until dismissed
### 2. Persistent Highlighting (Level 5000 Feature)
When user traces lineage:
1. Path lights up with glowing connections
2. User navigates to parent component
3. **Path STAYS LIT** on visible nodes
4. Indicator shows "Path continues in child: [Component Name]"
5. Click [×] to dismiss
### 3. Real-Time Updates
All views sync in real-time with:
- Canvas changes (add/remove nodes)
- Runtime state (live values)
- Preview interactions
### 4. State Persistence
View state persists across sessions:
- Active overlays
- Filter settings
- Traced paths
---
## Key Files & Locations
### Existing Code to Leverage
```
packages/noodl-editor/src/editor/src/
├── contexts/
│ └── NodeGraphContext/ # Already provides switchToComponent()
├── models/
│ ├── sidebar/sidebarmodel.tsx # Already supports React panels
│ └── componentmodel.ts # Component data
├── views/
│ └── nodegrapheditor/
│ ├── nodegrapheditor.ts # Main canvas (2000+ lines)
│ └── commentlayer.ts # TEMPLATE for overlays!
```
### New Code Locations
```
packages/noodl-editor/src/editor/src/
├── utils/
│ └── graphAnalysis/ # VIEW-000 utilities
│ ├── traversal.ts
│ ├── crossComponent.ts
│ ├── categorization.ts
│ └── duplicateDetection.ts
├── views/
│ ├── MetaViews/ # Meta view tab system
│ │ ├── MetaViewTabs.tsx
│ │ ├── TopologyMapView/
│ │ └── TriggerChainView/
│ ├── CanvasOverlays/ # Canvas overlay system
│ │ ├── CanvasOverlayManager.tsx
│ │ ├── LayersOverlay/
│ │ ├── LineageOverlay/
│ │ └── ImpactOverlay/
│ └── panels/ # Sidebar panels (existing pattern)
│ ├── XRayPanel/
│ └── CensusPanel/
```
---
## Testing Strategy
### For Each View
1. **Unit tests** for graph analysis utilities
2. **Integration tests** for view rendering
3. **Manual testing** with complex real projects
### Test Projects
Use projects with:
- Deep component nesting (5+ levels)
- Cross-component data flow
- Duplicate node names
- Complex event chains
---
## Common Patterns
### Registering a Sidebar Panel
```typescript
SidebarModel.instance.register({
id: 'xray',
name: 'X-Ray',
icon: IconName.Search,
panel: ComponentXRayPanel // React component
});
```
### Navigating to Canvas
```typescript
import { NodeGraphContextTmp } from '@noodl-contexts/NodeGraphContext';
// Switch to component
NodeGraphContextTmp.switchToComponent(component, {
node: nodeId, // Optional: select specific node
zoomToFit: true
});
```
### Accessing Project Data
```typescript
import { ProjectModel } from '@noodl-models/projectmodel';
// Get all components
ProjectModel.instance.getComponents();
// Get component by name
ProjectModel.instance.getComponentWithName('/MyComponent');
// Get current component
nodeGraph.activeComponent;
```
---
## Troubleshooting
### "Changes not loading"
→ Webpack caching issue. See PREREQ-001.
### "ReactDOM.render is not a function"
→ React 19 migration incomplete. See PREREQ-002.
### "Debug inspector crashes"
→ Legacy React patterns in debug code. See PREREQ-002.
### "Canvas overlay not responding to zoom"
→ Check CommentLayer pattern for transform handling.
---
## Success Criteria
For the complete project:
- [ ] Prerequisites resolved (webpack, React 19, overlay pattern, highlighting API)
- [ ] VIEW-000 foundation complete and tested
- [ ] All 7 views implemented and functional
- [ ] Persistent highlighting works across component navigation
- [ ] Real-time updates working
- [ ] State persists across sessions
- [ ] No regressions in existing canvas functionality
- [ ] Performance acceptable with large projects (100+ components)
---
## Reference Documents
- **[README.md](./README.md)** - Project overview and architecture
- **[VIEW-PREREQ-modernization-roadmap.md](./VIEW-PREREQ-modernization-roadmap.md)** - Prerequisites detail
- **[VIEW-000-foundation/README.md](./VIEW-000-foundation/README.md)** - Foundation implementation
- **Individual VIEW-XXX folders** - Detailed specs for each view
---
## Questions?
If you encounter blockers or need clarification:
1. Check the individual VIEW-XXX README for detailed specs
2. Reference existing patterns in codebase (CommentLayer, SidebarModel)
3. Document discoveries in a LEARNINGS.md file for future reference

View File

@@ -0,0 +1,188 @@
# PREREQ-001: Fix Webpack 5 Persistent Caching
## Overview
**Priority:** 🔴 CRITICAL - Blocks ALL development
**Estimate:** 1-2 days
**Status:** Not started
---
## The Problem
Webpack 5 persistent caching is preventing code changes from loading during development. When you modify a file, the old cached version continues to be served instead of the new code.
This was discovered during the ComponentsPanel React migration (TASK-004B) and is documented in:
`dev-docs/tasks/phase-2/TASK-004B-componentsPanel-react-migration/STATUS-BLOCKED.md`
### Symptoms
1. You modify a TypeScript/JavaScript file
2. You rebuild or let hot reload trigger
3. The browser shows the OLD code, not your changes
4. Console may show stale behavior
5. `console.log` statements you add don't appear
### Impact
Without fixing this, you cannot:
- Test any code changes reliably
- Develop any new features
- Debug existing issues
- Verify that fixes work
---
## Investigation Steps
### 1. Locate Webpack Configuration
```bash
# Find webpack config files
find packages -name "webpack*.js" -o -name "webpack*.ts"
# Common locations:
# packages/noodl-editor/webpack.config.js
# packages/noodl-editor/webpack.renderer.config.js
```
### 2. Check Current Cache Settings
Look for:
```javascript
module.exports = {
cache: {
type: 'filesystem', // This is the culprit
// ...
}
}
```
### 3. Verify It's a Caching Issue
```bash
# Clear all caches and rebuild
rm -rf node_modules/.cache
rm -rf packages/noodl-editor/node_modules/.cache
npm run build -- --no-cache
```
If changes appear after clearing cache, caching is confirmed as the issue.
---
## Solution Options
### Option A: Disable Persistent Caching in Dev (Recommended)
```javascript
// webpack.config.js
module.exports = (env) => ({
// Only use filesystem cache in production
cache: env.production ? {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
} : false, // No caching in development
// ... rest of config
});
```
**Pros:** Simple, guaranteed to work
**Cons:** Slower dev builds (but correctness > speed)
### Option B: Configure Proper Cache Invalidation
```javascript
// webpack.config.js
module.exports = {
cache: {
type: 'filesystem',
version: `${Date.now()}`, // Force cache bust
buildDependencies: {
config: [__filename],
// Add all config files that should invalidate cache
},
// Invalidate on file changes
managedPaths: [],
immutablePaths: [],
},
};
```
**Pros:** Keeps caching benefits when appropriate
**Cons:** More complex, may still have edge cases
### Option C: Memory Cache Only
```javascript
// webpack.config.js
module.exports = {
cache: {
type: 'memory',
maxGenerations: 1,
},
};
```
**Pros:** Fast rebuilds within session, no persistence bugs
**Cons:** Every new terminal session starts cold
---
## Implementation Steps
1. **Identify all webpack config files** in the project
2. **Check if there are separate dev/prod configs**
3. **Implement Option A** (safest starting point)
4. **Test the fix:**
- Make a visible change to a component
- Rebuild
- Verify change appears in browser
5. **Test hot reload:**
- Start dev server
- Make change
- Verify hot reload picks it up
6. **Document the change** in LEARNINGS.md
---
## Verification Checklist
- [ ] Code changes appear after rebuild
- [ ] Hot reload reflects changes immediately
- [ ] Console.log statements added to code appear in browser console
- [ ] No stale code behavior
- [ ] Build times acceptable (document before/after if significant)
- [ ] Works across terminal restarts
- [ ] Works after system restart
---
## Files Likely to Modify
```
packages/noodl-editor/webpack.config.js
packages/noodl-editor/webpack.renderer.config.js
packages/noodl-editor/webpack.main.config.js (if exists)
```
---
## Related Issues
- TASK-004B ComponentsPanel migration blocked by this
- Any future development work blocked by this
- PREREQ-002, PREREQ-003, PREREQ-004 all blocked by this
---
## Success Criteria
1. Can modify any TypeScript/JavaScript file
2. Changes appear immediately after rebuild
3. Hot reload works correctly
4. No need to manually clear caches
5. Works consistently across multiple dev sessions

View File

@@ -0,0 +1,227 @@
# PREREQ-002: React 19 Debug Infrastructure Fixes
## Overview
**Priority:** HIGH
**Estimate:** 0.5-1 day
**Status:** Not started
**Blocked by:** PREREQ-001 (Webpack caching)
---
## The Problem
After the React 19 migration, several files still use legacy React 17/16 APIs that have been removed:
- `ReactDOM.render()` - Removed in React 18+
- `ReactDOM.unmountComponentAtNode()` - Removed in React 18+
- Creating new `createRoot()` on every render instead of reusing
These cause crashes in the debug inspector system, which is needed for:
- VIEW-003: Trigger Chain Debugger
- VIEW-005: Data Lineage (live values)
---
## Error Messages You'll See
```
ReactDOM.render is not a function
at DebugInspectorPopup.render (nodegrapheditor.debuginspectors.js:60)
ReactDOM.unmountComponentAtNode is not a function
at DebugInspectorPopup.dispose (nodegrapheditor.debuginspectors.js:64)
You are calling ReactDOMClient.createRoot() on a container that has already
been passed to createRoot() before.
at _renderReact (commentlayer.ts:145)
```
---
## Files to Fix
### 1. nodegrapheditor.debuginspectors.js
**Location:** `packages/noodl-editor/src/editor/src/views/nodegrapheditor/`
**Current (broken):**
```javascript
const ReactDOM = require('react-dom');
class DebugInspectorPopup {
render() {
ReactDOM.render(<InspectorComponent />, this.container);
}
dispose() {
ReactDOM.unmountComponentAtNode(this.container);
}
}
```
**Fixed:**
```javascript
import { createRoot } from 'react-dom/client';
class DebugInspectorPopup {
constructor() {
this.root = null;
}
render() {
if (!this.root) {
this.root = createRoot(this.container);
}
this.root.render(<InspectorComponent />);
}
dispose() {
if (this.root) {
this.root.unmount();
this.root = null;
}
}
}
```
### 2. commentlayer.ts
**Location:** `packages/noodl-editor/src/editor/src/views/nodegrapheditor/commentlayer.ts`
**Current (broken):**
```typescript
_renderReact() {
this.root = createRoot(this.div); // Creates new root every time!
this.root.render(<CommentLayerView ... />);
}
```
**Fixed:**
```typescript
_renderReact() {
if (!this.root) {
this.root = createRoot(this.div);
}
this.root.render(<CommentLayerView ... />);
}
dispose() {
if (this.root) {
this.root.unmount();
this.root = null;
}
}
```
### 3. TextStylePicker.jsx
**Location:** `packages/noodl-editor/src/editor/src/views/` (search for it)
**Same pattern as debuginspectors** - replace ReactDOM.render/unmountComponentAtNode with createRoot pattern.
---
## Implementation Steps
### Phase 1: Fix Debug Inspectors
1. Open `nodegrapheditor.debuginspectors.js`
2. Change `require('react-dom')` to `import { createRoot } from 'react-dom/client'`
3. Store root instance on the class
4. Reuse root on subsequent renders
5. Use `root.unmount()` on dispose
### Phase 2: Fix CommentLayer
1. Open `commentlayer.ts`
2. Add root instance check before creating
3. Ensure dispose properly unmounts
### Phase 3: Fix TextStylePicker
1. Find the file (search for TextStylePicker)
2. Apply same pattern
### Phase 4: Search for Other Instances
```bash
# Find any remaining legacy ReactDOM usage
grep -r "ReactDOM.render" packages/noodl-editor/src/
grep -r "unmountComponentAtNode" packages/noodl-editor/src/
```
Fix any additional instances found.
---
## The Pattern
**Before (React 17):**
```javascript
// Import
const ReactDOM = require('react-dom');
// or
import ReactDOM from 'react-dom';
// Render
ReactDOM.render(<Component />, container);
// Cleanup
ReactDOM.unmountComponentAtNode(container);
```
**After (React 18+):**
```javascript
// Import
import { createRoot } from 'react-dom/client';
// Store root (create once, reuse)
if (!this.root) {
this.root = createRoot(container);
}
// Render
this.root.render(<Component />);
// Cleanup
this.root.unmount();
```
---
## Verification Checklist
- [ ] Debug inspector popups appear without errors
- [ ] Hovering over connections shows value inspector
- [ ] Pinning inspectors works
- [ ] Comment layer renders without console errors
- [ ] Text style picker works (if applicable)
- [ ] No "createRoot called twice" warnings
- [ ] No "ReactDOM.render is not a function" errors
---
## Testing
1. Open any component in the canvas
2. Run the preview
3. Hover over a connection line
4. Debug inspector should appear
5. Check console for errors
---
## Related Documentation
- `dev-docs/tasks/phase-2/TASK-002-react19-ui-fixes/README.md`
- React 18 Migration Guide: https://react.dev/blog/2022/03/08/react-18-upgrade-guide
---
## Success Criteria
1. No React legacy API errors in console
2. Debug inspector fully functional
3. Comment layer renders properly
4. All existing functionality preserved

View File

@@ -0,0 +1,239 @@
# PREREQ-003: Document Canvas Overlay Pattern
## Overview
**Priority:** HIGH
**Estimate:** 1-2 days
**Status:** Not started
**Blocked by:** PREREQ-001 (Webpack caching)
**Can parallelize with:** PREREQ-002
---
## The Good News
**CommentLayer already works as a React overlay on the canvas!**
This proves the overlay pattern is viable. This task is about:
1. Understanding how CommentLayer works
2. Documenting the pattern
3. Creating reusable infrastructure for other overlays
---
## What CommentLayer Does
CommentLayer renders React components that:
- Float over the HTML5 Canvas
- Respond to pan/zoom (stay in correct position)
- Integrate with selection system
- Support user interaction (click, drag)
This is exactly what we need for:
- VIEW-005: Data Lineage (path highlighting + panel)
- VIEW-006: Impact Radar (dependency highlighting + panel)
- VIEW-007: Semantic Layers (node visibility filtering)
---
## Investigation Tasks
### 1. Study CommentLayer Implementation
**File:** `packages/noodl-editor/src/editor/src/views/nodegrapheditor/commentlayer.ts`
Document:
- How it creates the overlay container
- How it subscribes to pan/zoom changes
- How it transforms coordinates between canvas space and screen space
- How it handles React rendering lifecycle
### 2. Identify Integration Points
Find where CommentLayer connects to:
- NodeGraphEditor (the main canvas class)
- Pan/zoom events
- Selection events
- Mouse events
### 3. Extract Reusable Patterns
Create shared utilities that any overlay can use:
- Coordinate transformation
- Pan/zoom subscription
- Overlay container management
---
## Expected Findings
### Coordinate Transformation
The canvas has its own coordinate system. Overlays need to convert between:
- **Canvas coordinates** - Position in the node graph space
- **Screen coordinates** - Position on the user's screen
```typescript
// Expected pattern (to verify):
function canvasToScreen(point: Point, viewport: Viewport): Point {
return {
x: (point.x + viewport.pan.x) * viewport.scale,
y: (point.y + viewport.pan.y) * viewport.scale
};
}
function screenToCanvas(point: Point, viewport: Viewport): Point {
return {
x: point.x / viewport.scale - viewport.pan.x,
y: point.y / viewport.scale - viewport.pan.y
};
}
```
### Pan/Zoom Subscription
The overlay needs to re-render when viewport changes:
```typescript
// Expected pattern (to verify):
nodeGraphEditor.on('viewportChanged', ({ pan, scale }) => {
this.updateOverlayPositions(pan, scale);
});
```
### React Rendering
The overlay renders React over the canvas:
```typescript
// Expected pattern (to verify):
class CanvasOverlay {
private container: HTMLDivElement;
private root: Root;
constructor(parentElement: HTMLElement) {
this.container = document.createElement('div');
this.container.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none; // Allow clicks through to canvas
`;
parentElement.appendChild(this.container);
this.root = createRoot(this.container);
}
render(props: OverlayProps) {
this.root.render(<OverlayComponent {...props} />);
}
}
```
---
## Deliverables
### 1. Documentation
Create `CANVAS-OVERLAY-PATTERN.md` documenting:
- How CommentLayer works
- The coordinate transformation system
- The event subscription pattern
- Common gotchas and solutions
### 2. Shared Infrastructure
Create reusable overlay utilities:
```typescript
// packages/noodl-editor/src/editor/src/views/CanvasOverlays/
// CanvasOverlayBase.ts - Base class for overlays
export abstract class CanvasOverlayBase {
protected viewport: Viewport;
protected root: Root;
abstract render(): void;
protected canvasToScreen(point: Point): Point;
protected screenToCanvas(point: Point): Point;
protected subscribeToViewport(callback: ViewportCallback): Unsubscribe;
}
// OverlayContainer.tsx - React component for overlay positioning
export function OverlayContainer({
children,
viewport
}: OverlayContainerProps): JSX.Element;
// useViewportTransform.ts - Hook for overlays
export function useViewportTransform(): {
pan: Point;
scale: number;
canvasToScreen: (point: Point) => Point;
screenToCanvas: (point: Point) => Point;
};
```
### 3. Example Implementation
Create a minimal test overlay that:
- Renders a simple React component over the canvas
- Updates position correctly on pan/zoom
- Handles click events properly
---
## Implementation Steps
### Phase 1: Study (4-6 hours)
1. Read through `commentlayer.ts` thoroughly
2. Add comments explaining each section
3. Trace the data flow from canvas events to React render
4. Identify all integration points
### Phase 2: Document (2-4 hours)
1. Write `CANVAS-OVERLAY-PATTERN.md`
2. Include code examples
3. Document gotchas discovered
### Phase 3: Extract Utilities (4-6 hours)
1. Create `CanvasOverlays/` directory
2. Extract coordinate transformation utilities
3. Create base class or hooks for overlays
4. Write tests for utilities
### Phase 4: Verify (2 hours)
1. Create minimal test overlay
2. Verify it works with pan/zoom
3. Verify click handling works
---
## Files to Study
```
packages/noodl-editor/src/editor/src/views/nodegrapheditor/
├── commentlayer.ts # Main overlay implementation
├── CommentLayer/
│ ├── CommentLayerView.tsx # React component
│ ├── CommentForeground.tsx
│ └── CommentBackground.tsx
└── nodegrapheditor.ts # Integration point
```
---
## Success Criteria
1. CommentLayer pattern fully documented
2. Coordinate transformation utilities created and tested
3. Base overlay class/hooks created
4. Test overlay works correctly with pan/zoom
5. Documentation sufficient for implementing VIEW-005, 006, 007

View File

@@ -0,0 +1,341 @@
# PREREQ-004: Canvas Highlighting API
## Overview
**Priority:** HIGH
**Estimate:** 1-2 days
**Status:** Not started
**Blocked by:** VIEW-000 (Foundation), PREREQ-003 (Overlay Pattern)
---
## The Goal
Create an API for **persistent, multi-channel highlighting** on the canvas.
This is the "Level 5000" feature that makes Data Lineage and Impact Radar legendary:
- Highlights persist until explicitly dismissed
- Multiple highlight channels can coexist (lineage = blue, impact = orange)
- Highlights persist across component navigation
---
## Current State
The canvas already has some highlighting:
- Nodes flash when they fire (debug visualization)
- Connections animate during data flow
- Selection highlighting exists
But these are:
- Temporary (fade after a few seconds)
- Single-purpose (can't have multiple types at once)
- Component-local (don't persist across navigation)
---
## Required API
```typescript
// canvasHighlight.ts
export interface CanvasHighlightAPI {
// Create highlights (returns handle to control them)
highlightNodes(
nodeIds: string[],
options: HighlightOptions
): HighlightHandle;
highlightConnections(
connections: ConnectionRef[],
options: HighlightOptions
): HighlightHandle;
highlightPath(
path: PathDefinition,
options: HighlightOptions
): HighlightHandle;
// Query
getActiveHighlights(): HighlightInfo[];
getHighlightsForChannel(channel: string): HighlightInfo[];
// Clear
clearChannel(channel: string): void;
clearAll(): void;
}
export interface HighlightOptions {
channel: string; // 'lineage', 'impact', 'selection', etc.
color?: string; // Override default channel color
style?: 'solid' | 'pulse' | 'glow';
persistent?: boolean; // Stay until dismissed (default: true)
label?: string; // Optional label near highlight
}
export interface HighlightHandle {
id: string;
channel: string;
// Control
update(nodeIds: string[]): void; // Change what's highlighted
setLabel(label: string): void;
dismiss(): void;
// Query
isActive(): boolean;
getNodeIds(): string[];
}
export interface PathDefinition {
nodes: string[]; // Ordered node IDs in the path
connections: ConnectionRef[]; // Connections between them
crossesComponents?: boolean;
componentBoundaries?: ComponentBoundary[];
}
export interface ConnectionRef {
fromNodeId: string;
fromPort: string;
toNodeId: string;
toPort: string;
}
export interface ComponentBoundary {
componentName: string;
entryNodeId?: string; // Component Input
exitNodeId?: string; // Component Output
}
```
---
## Channel System
Different highlight channels for different purposes:
```typescript
const HIGHLIGHT_CHANNELS = {
lineage: {
color: '#4A90D9', // Blue
style: 'glow',
description: 'Data lineage traces'
},
impact: {
color: '#F5A623', // Orange
style: 'pulse',
description: 'Impact/dependency highlights'
},
selection: {
color: '#FFFFFF', // White
style: 'solid',
description: 'Current selection'
},
warning: {
color: '#FF6B6B', // Red
style: 'pulse',
description: 'Issues or duplicates'
}
};
```
Channels can coexist - a node can have both lineage AND impact highlights.
---
## Persistence Across Navigation
**The key feature:** Highlights persist when you navigate to different components.
### Implementation Approach
```typescript
// Global highlight state (not per-component)
class HighlightManager {
private highlights: Map<string, HighlightState> = new Map();
// When component changes, update what's visible
onComponentChanged(newComponent: ComponentModel) {
this.highlights.forEach((state, id) => {
// Find which nodes in this highlight are in the new component
state.visibleNodes = state.allNodes.filter(
nodeId => this.nodeExistsInComponent(nodeId, newComponent)
);
// Update the visual highlighting
this.updateVisualHighlight(id);
});
}
}
```
### Cross-Component Indicators
When a highlight path crosses component boundaries:
```
┌─────────────────────────────────────────────────────────────┐
│ 🔗 Lineage: messageText │
│ │
│ ⬆️ Path continues in parent: App Shell [Go ↗] │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ REST /api/user → userData.name → String Format → ★ HERE │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ │
│ ⬇️ Path continues in child: TextField [Go ↗] │
└─────────────────────────────────────────────────────────────┘
```
---
## Rendering Highlights
### Option A: Canvas-Based (Recommended)
Modify the canvas paint loop to render highlights:
```typescript
// In NodeGraphEditor paint cycle
paintHighlights() {
const highlights = HighlightManager.getHighlightsForCurrentComponent();
highlights.forEach(highlight => {
highlight.visibleNodes.forEach(nodeId => {
const node = this.getNodeById(nodeId);
this.paintNodeHighlight(node, highlight.options);
});
highlight.visibleConnections.forEach(conn => {
this.paintConnectionHighlight(conn, highlight.options);
});
});
}
```
**Pros:** Native canvas rendering, performant
**Cons:** Requires modifying NodeGraphEditor paint loop
### Option B: Overlay-Based
Use the canvas overlay system (PREREQ-003) to render highlights as a layer:
```typescript
// HighlightOverlay.tsx
function HighlightOverlay({ highlights, viewport }) {
return (
<svg style={{ position: 'absolute', pointerEvents: 'none' }}>
{highlights.map(h => (
<HighlightPath key={h.id} path={h} viewport={viewport} />
))}
</svg>
);
}
```
**Pros:** Uses overlay infrastructure, easier to implement
**Cons:** May have z-order issues with canvas content
### Recommendation
Start with **Option B** (overlay-based) for faster implementation, then optimize to **Option A** if performance requires.
---
## Implementation Steps
### Phase 1: Core API (4-6 hours)
1. Create `canvasHighlight.ts` with TypeScript interfaces
2. Implement `HighlightManager` singleton
3. Implement channel system
4. Add highlight state storage
### Phase 2: Visual Rendering (4-6 hours)
1. Create `HighlightOverlay` component (using overlay pattern from PREREQ-003)
2. Implement node highlighting visuals
3. Implement connection highlighting visuals
4. Implement glow/pulse effects
### Phase 3: Persistence (2-4 hours)
1. Hook into component navigation
2. Update visible nodes on component change
3. Create boundary indicators UI
### Phase 4: Integration (2-4 hours)
1. Expose API to view components
2. Create hooks for easy use: `useHighlight()`
3. Test with sample data
---
## Files to Create
```
packages/noodl-editor/src/editor/src/
├── services/
│ └── HighlightManager/
│ ├── index.ts
│ ├── HighlightManager.ts
│ ├── types.ts
│ └── channels.ts
└── views/
└── CanvasOverlays/
└── HighlightOverlay/
├── index.ts
├── HighlightOverlay.tsx
├── NodeHighlight.tsx
├── ConnectionHighlight.tsx
└── BoundaryIndicator.tsx
```
---
## Usage Example
```typescript
// In Data Lineage view
function traceLineage(nodeId: string) {
const path = graphAnalysis.traceUpstream(nodeId);
const handle = highlightAPI.highlightPath(path, {
channel: 'lineage',
style: 'glow',
persistent: true,
label: `Lineage for ${nodeName}`
});
// Store handle to dismiss later
setActiveLineageHandle(handle);
}
// When user clicks dismiss
function dismissLineage() {
activeLineageHandle?.dismiss();
}
```
---
## Verification Checklist
- [ ] Can highlight individual nodes
- [ ] Can highlight connections
- [ ] Can highlight entire paths
- [ ] Multiple channels work simultaneously
- [ ] Highlights persist across component navigation
- [ ] Boundary indicators show correctly
- [ ] Dismiss button works
- [ ] Performance acceptable with many highlights
---
## Success Criteria
1. Highlighting API fully functional
2. Glow/pulse effects visually appealing
3. Persists across navigation
4. Multiple channels coexist
5. Easy to use from view components
6. Performance acceptable

View File

@@ -0,0 +1,430 @@
# Project: Canvas Visualization Views
> **🤖 For Cline:** Start with **[CLINE-INSTRUCTIONS.md](./CLINE-INSTRUCTIONS.md)** for implementation guidance.
## Overview
**Goal:** Create a suite of complementary read-only views that help users understand complex node graphs without changing the core canvas editor. These views transform the same underlying data into different visual representations optimized for different questions.
**Status:** 📋 Ready for Implementation
**Total Effort:** 30-41 days (including prerequisites)
**Priority:** HIGH
**Why this matters:**
- Complex Noodl projects become unmanageable "node spaghetti" that's hard to understand
- The node canvas optimizes for *editing* but not for *understanding*
- Users spend enormous time zooming in/out, hunting for nodes, tracing connections
- Understanding another developer's project is currently a nightmare
- Debugging cross-component issues requires mental gymnastics
**Core Philosophy:**
- Read-only views (simplicity, no edit conflicts)
- Same data, different projections (all views derive from ComponentModel/NodeGraphModel)
- "Jump to canvas" from any view (views complement, don't replace)
- Progressive disclosure (start simple, drill down as needed)
- **Users love the existing canvas** - enhance it, don't replace it
---
## The Problem (Visual Evidence)
Complex Noodl canvases exhibit these pain points:
1. **Spatial chaos** - Logic nodes scattered among visual nodes with no organization
2. **Connection noise** - Noodles become meaningless lines when there are dozens
3. **Invisible boundaries** - Component boundaries aren't visible on canvas
4. **Duplicate confusion** - Same-named nodes in multiple places cause bugs
5. **Origin mystery** - "Where does this value actually come from?"
6. **Impact blindness** - "What will break if I change this?"
7. **Trigger tracing** - Following event chains across components is manual
---
## Proposed Views
| # | View Name | Type | Primary Question Answered | Complexity |
|---|-----------|------|---------------------------|------------|
| 1 | **Project Topology Map** | 🗺️ Meta View | "How is this project organized?" | Medium |
| 2 | **Component X-Ray** | 📋 Sidebar Panel | "What does this component do?" | Low |
| 3 | **Trigger Chain Debugger** | 🗺️ Meta View | "What's the sequence of events?" | High |
| 4 | **Node Census** | 📋 Sidebar Panel | "What nodes exist and are any duplicated?" | Low |
| 5 | **Data Lineage View** | 🎨 Canvas Overlay | "Where does this value originate?" | Medium |
| 6 | **Impact Radar** | 🎨 Canvas Overlay | "What breaks if I change this?" | Medium |
| 7 | **Semantic Layers** | 🎨 Canvas Overlay | "Can I see just the logic/just the visuals?" | Low |
### View Types Explained
**🗺️ Meta Views** - Replace the canvas entirely with a project-wide or timeline view
- Component panel still exists but works differently (click to highlight, not navigate)
- Examples: Topology Map (see all components), Trigger Chain (see recorded timeline)
**📋 Sidebar Panels** - Open alongside the existing canvas
- Don't replace anything, add information panels
- Click items to highlight/navigate on canvas
- Examples: X-Ray (component summary), Census (node inventory)
**🎨 Canvas Overlays** - Enhance the existing canvas you're already in
- Toggle buttons that ADD visualization to the beloved node canvas
- Persistent highlighting until dismissed
- Examples: Layers (filter visibility), Lineage (highlight data paths), Impact (show dependencies)
---
## Implementation Strategy
### Phase 1: Foundation (do first)
Build shared infrastructure that all views need:
- Graph traversal utilities
- Cross-component connection resolution
- View panel framework / tab system
- "Jump to canvas" navigation helper
### Phase 2: Quick Wins (low complexity, high value)
- VIEW-002: Component X-Ray
- VIEW-004: Node Census
- VIEW-007: Semantic Layers
### Phase 3: Core Value (medium complexity)
- VIEW-001: Project Topology Map
- VIEW-005: Data Lineage View
- VIEW-006: Impact Radar
### Phase 4: Advanced (high complexity)
- VIEW-003: Trigger Chain Debugger
---
## Technical Foundation
All views will leverage existing data structures:
```typescript
// Available from ComponentModel
component.graph.forEachNode((node) => { ... })
component.graph.findNodeWithId(id)
component.getAllConnections()
component.getPorts() // Component inputs/outputs
// Available from NodeGraphNode
node.type // Node type info
node.parameters // Current parameter values
node.getPorts() // Input/output ports
node.parent // Visual hierarchy (for visual nodes)
node.children // Visual hierarchy (for visual nodes)
node.forAllConnectionsOnThisNode((connection) => { ... })
// Connection structure
{
fromId: string,
fromProperty: string, // output port name
toId: string,
toProperty: string // input port name
}
```
### Shared Utilities to Build
```typescript
// packages/noodl-editor/src/editor/src/utils/graphAnalysis/
// Traverse connections across component boundaries
traceConnectionChain(startNode, startPort, direction: 'upstream' | 'downstream')
// Find all usages of a component across the project
findComponentUsages(componentName: string): ComponentUsage[]
// Detect potential duplicate nodes
findPotentialDuplicates(component): DuplicateGroup[]
// Build component dependency graph
buildComponentDependencyGraph(project): DependencyGraph
// Categorize nodes by semantic type
categorizeNodes(component): CategorizedNodes
```
---
## UI Framework
Views will be implemented as React components accessible via:
1. **Sidebar Panel** - New "Analysis" or "Views" panel in the sidebar
2. **Keyboard Shortcut** - Quick access (e.g., `Cmd+Shift+V` for views menu)
3. **Context Menu** - Right-click node → "Show in Data Lineage" etc.
### View Panel Structure
```
┌─────────────────────────────────────────────────────────────┐
│ [Topology] [Census] [Lineage] [Impact] [Layers] [×] │
├─────────────────────────────────────────────────────────────┤
│ │
│ View Content Here │
│ │
│ │
├─────────────────────────────────────────────────────────────┤
│ Context: Presales Page | [Jump to Canvas] │
└─────────────────────────────────────────────────────────────┘
```
---
## Success Criteria (Project-Level)
1. **Comprehension time reduced** - New developer can understand a complex page in minutes, not hours
2. **Bug discovery improved** - Duplicate nodes and connection issues are surfaced automatically
3. **Cross-component debugging possible** - Can trace data/events across component boundaries
4. **No canvas changes required** - All views are additive, read-only
5. **Performance acceptable** - Views render quickly even for large projects
---
## Prerequisites & Dependencies
**See [VIEW-PREREQ: Prerequisites & Modernization Roadmap](./VIEW-PREREQ-modernization-roadmap.md) for full details.**
### Critical Blockers
| Prerequisite | Why It's Needed | Estimate |
|--------------|-----------------|----------|
| **PREREQ-001: Webpack Caching Fix** | Can't test ANY changes reliably | 1-2 days |
| **PREREQ-002: React 19 Debug Fixes** | Debug infrastructure crashes, needed for Trigger Chain | 0.5-1 day |
### Required for Canvas Overlays
| Prerequisite | Why It's Needed | Estimate |
|--------------|-----------------|----------|
| **PREREQ-003: Document Overlay Pattern** | CommentLayer already works - formalize it | 1-2 days |
| **PREREQ-004: Canvas Highlighting API** | Persistent highlighting for Lineage/Impact | 1-2 days |
### Nice to Have
| Prerequisite | Why It's Needed | Estimate |
|--------------|-----------------|----------|
| **PREREQ-005: Complete ComponentsPanel** | Badges, highlighting in left panel | 2-3 days |
| **PREREQ-006: NodeGraphEditor Modernization** | Cleaner integration (not required) | 5-10 days |
### The Good News 🎉
**CommentLayer already exists as a React overlay on the canvas!** This proves the overlay pattern works. We just need to formalize it and extend it for our views.
### What's Already Ready
- ✅ SidebarModel - supports React panels
- ✅ NodeGraphContext - provides `switchToComponent()` navigation
- ✅ CommentLayer - working canvas overlay template
- ✅ ProjectModel - graph data access
---
## Estimated Total Effort (Including Prerequisites)
### Prerequisites (~5-7 days with parallelization)
| Task | Estimate |
|------|----------|
| PREREQ-001: Webpack caching fix | 1-2 days |
| PREREQ-002: React 19 debug fixes | 0.5-1 day |
| PREREQ-003: Document overlay pattern | 1-2 days |
| PREREQ-004: Canvas highlighting API | 1-2 days |
| **Prerequisites Total** | **~5-7 days** |
### Views
| Task | Estimate |
|------|----------|
| VIEW-000: Foundation & Shared Utils | 4-5 days |
| VIEW-001: Project Topology Map | 4-5 days |
| VIEW-002: Component X-Ray | 2-3 days |
| VIEW-003: Trigger Chain Debugger | 5-7 days |
| VIEW-004: Node Census | 2-3 days |
| VIEW-005: Data Lineage View | 3-4 days |
| VIEW-006: Impact Radar | 3-4 days |
| VIEW-007: Semantic Layers | 2-3 days |
| **Views Total** | **25-34 days** |
### Grand Total: **30-41 days**
---
## Documentation
### For Implementation
- **[CLINE-INSTRUCTIONS.md](./CLINE-INSTRUCTIONS.md)** ← Implementation guide for Cline
- **[VIEW-PREREQ: Prerequisites Overview](./VIEW-PREREQ-modernization-roadmap.md)** ← What to fix before starting
### Prerequisite Tasks
- [PREREQ-001: Webpack Caching Fix](./PREREQ-001-webpack-caching/README.md) - 🔴 CRITICAL, do first
- [PREREQ-002: React 19 Debug Fixes](./PREREQ-002-react19-debug-fixes/README.md)
- [PREREQ-003: Canvas Overlay Pattern](./PREREQ-003-canvas-overlay-pattern/README.md)
- [PREREQ-004: Canvas Highlighting API](./PREREQ-004-highlighting-api/README.md)
### Task Specifications
- [VIEW-000: Foundation & Shared Utilities](./VIEW-000-foundation/README.md) - **START HERE** (blocks all others)
- [VIEW-001: Project Topology Map](./VIEW-001-project-topology-map/README.md) - 🗺️ Meta View
- [VIEW-002: Component X-Ray](./VIEW-002-component-xray/README.md) - 📋 Sidebar Panel
- [VIEW-003: Trigger Chain Debugger](./VIEW-003-trigger-chain-debugger/README.md) - 🗺️ Meta View
- [VIEW-004: Node Census](./VIEW-004-node-census/README.md) - 📋 Sidebar Panel
- [VIEW-005: Data Lineage View](./VIEW-005-data-lineage/README.md) - 🎨 Canvas Overlay
- [VIEW-006: Impact Radar](./VIEW-006-impact-radar/README.md) - 🎨 Canvas Overlay
- [VIEW-007: Semantic Layers](./VIEW-007-semantic-layers/README.md) - 🎨 Canvas Overlay
---
## Runtime Integration
**Noodl's killer feature is the live debugging** - nodes light up, connections animate, and you can see data flow in real-time when interacting with the preview. Some views need to integrate with this existing infrastructure.
### Runtime Integration Matrix
| View | Needs Runtime? | Why |
|------|----------------|-----|
| VIEW-000 Foundation | ⚠️ Document existing | Must understand debug infrastructure |
| VIEW-001 Topology Map | ❌ No | Static component relationships |
| VIEW-002 Component X-Ray | ❌ No | Static component analysis |
| VIEW-003 Trigger Chain | ✅ **CRITICAL** | Records live events across components |
| VIEW-004 Node Census | ❌ No | Static node inventory |
| VIEW-005 Data Lineage | ⚠️ Optional | Static paths, but live values enhance it |
| VIEW-007 Semantic Layers | ⚠️ Preserve | Must not break existing highlighting |
### Existing Infrastructure to Leverage
The editor already has powerful debugging:
```typescript
// Debug Inspector - live value inspection
DebugInspector.instance.getValueForPort(nodeId, port);
// Node/Connection highlighting
nodeGraphEditor.highlightNode(node, duration);
nodeGraphEditor.highlightConnection(connection, duration);
// Runtime events (nodes emit these)
nodeInstance.on('outputChanged', (port, value) => { ... });
nodeInstance.on('signalSent', (port) => { ... });
```
### The Gap These Views Fill
**Current state**: You can see what's happening NOW in ONE component. To see the full flow, you manually navigate between components, losing context.
**With new views**: You can see what happened OVER TIME across ALL components simultaneously. Record → Review → Understand → Debug.
---
## Design Decisions
### 1. Three Types of Views
Rather than one unified view switcher, we have three distinct types:
#### 🗺️ Meta Views (Tab in Header)
Full-screen views that replace the canvas entirely:
```
┌─────────────────────────────────────────────────────────────────┐
│ [🗺️ Canvas] [📊 Topology] [⚡ Triggers] [Preview ▶]│
├─────────────────────────────────────────────────────────────────┤
│ │
│ Topology Map / Trigger Timeline │
│ (full canvas area) │
│ │
└─────────────────────────────────────────────────────────────────┘
```
- **Topology Map** - See all components and their relationships
- **Trigger Chain** - See recorded event timeline across components
#### 📋 Sidebar Panels
Information panels that open alongside the canvas (like the existing Components panel):
- **X-Ray** - Component summary with inputs/outputs/dependencies
- **Census** - Node inventory with duplicate warnings
These use the existing SidebarModel system - already React-ready!
#### 🎨 Canvas Overlays (THE GAME CHANGER)
Enhancements to the existing node canvas that users already love:
```
┌─────────────────────────────────────────────────────────────────┐
│ Login Page [Layers] [Lineage ✓] [X-Ray]│
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │REST ████│════════════════════│Object███│══╗ ← Glowing │
│ │/api/user│ highlighted │userData │ ║ path! │
│ └─────────┘ connection └─────────┘ ║ │
│ ║ │
│ ┌──────────╨────────┐ │
│ │String Format █████│ │
│ └───────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ 🔗 Lineage: messageText [×] │
│ REST /api/user → userData.name → String Format → ★ HERE │
│ ⬆️ Trace continues in parent: App Shell [Go ↗] │
└─────────────────────────────────────────────────────────────────┘
```
**Key insight:** CommentLayer already proves this works! It's a React overlay on the canvas that responds to zoom/pan.
Overlays include:
- **Semantic Layers** - Filter what's visible (hide logic, show only data, etc.)
- **Data Lineage** - Highlight data flow paths WITH PERSISTENT HIGHLIGHTING
- **Impact Radar** - Highlight what depends on selected node
### 2. Persistent Highlighting (Level 5000)
When you trace lineage or impact, the highlighting STAYS on the canvas until you dismiss it:
- Right-click node → "Trace Data Lineage"
- Path lights up on canvas (glowing connections!)
- Small panel shows full path including cross-component parts
- Navigate to parent component → PATH STAYS LIT
- Click [×] on the overlay panel to dismiss
Multiple highlight "channels" can coexist:
- Blue glow = lineage path
- Orange glow = impact/dependencies
- Selection highlight = normal selection
### 3. Live Updates: Yes, Real-Time Sync
All views update in **real-time**, staying in sync with:
- Canvas changes (add/remove nodes, connections)
- Runtime state (live values, node activation)
- Preview interactions (for Trigger Chain recording)
This matches the current canvas/preview sync that makes Noodl magical.
### 4. Persistence: Yes
View state persists across sessions:
- Which overlays are active
- Expanded/collapsed sections
- Filter settings (Semantic Layers selections)
- Currently traced lineage paths
Stored per-component (different components can have different states).
---
## Open Questions (Remaining)
1. **Meta View Tab Location** - Where exactly should the [Canvas] [Topology] [Triggers] tabs go? In the existing header row? Replace breadcrumbs? New row above?
2. **Overlay Toggle Location** - Where do the [Layers] [Lineage] [Impact] toggle buttons go? Top-right of canvas area? Floating toolbar? Sidebar?
3. **Highlight Persistence Indicator** - When lineage highlighting persists across component navigation, should there be a visible indicator showing "You have active traces"? Badge somewhere?
4. **Multi-Component Lineage Display** - When a lineage path crosses 3+ components, and you're viewing one component, how do we show "trace continues in parent/child"? Mini-map? Breadcrumb-style path indicator?
5. **Overlay Conflicts** - Can you have Layers AND Lineage active simultaneously? If Layers hides a node that's in the lineage path, what happens? (Probably: lineage overrides layers for highlighted nodes)

View File

@@ -0,0 +1,833 @@
# VIEW-000: Foundation & Shared Utilities
## Overview
Build the shared infrastructure that all visualization views will depend on: graph traversal utilities, cross-component resolution, the view panel framework, and navigation helpers.
**Estimate:** 4-5 days
**Priority:** CRITICAL (blocks all other VIEW tasks)
**Complexity:** Medium
---
## Goals
1. Create graph analysis utilities for traversing nodes and connections
2. Build cross-component connection resolution (follow Component Inputs/Outputs)
3. Implement the view panel framework (tabbed container for all views)
4. Create "Jump to Canvas" navigation helper
5. Establish patterns and types for view implementations
---
## Why Foundation First?
Every view needs to:
- Traverse the node graph in some way
- Potentially cross component boundaries
- Display in a consistent panel/container
- Allow navigation back to the canvas
Building this once, correctly, saves massive duplication and ensures consistency.
### 4. Real-Time Update System
Views must stay in sync with canvas changes and runtime state. This requires a subscription system.
```typescript
// viewUpdates.ts
export interface ViewUpdateSource {
// Canvas/model changes
onNodeAdded: (callback: (node: NodeGraphNode) => void) => Unsubscribe;
onNodeRemoved: (callback: (nodeId: string) => void) => Unsubscribe;
onConnectionAdded: (callback: (conn: Connection) => void) => Unsubscribe;
onConnectionRemoved: (callback: (conn: Connection) => void) => Unsubscribe;
onNodeParameterChanged: (callback: (nodeId: string, param: string, value: unknown) => void) => Unsubscribe;
// Component changes
onComponentSwitched: (callback: (component: ComponentModel) => void) => Unsubscribe;
// Runtime events (for views that need them)
onRuntimeEvent: (callback: (event: RuntimeEvent) => void) => Unsubscribe;
}
// Hook for views to subscribe to updates
export function useViewUpdates(
dependencies: ('nodes' | 'connections' | 'runtime')[],
callback: () => void
): void {
useEffect(() => {
const unsubscribes: Unsubscribe[] = [];
if (dependencies.includes('nodes')) {
unsubscribes.push(viewUpdateSource.onNodeAdded(callback));
unsubscribes.push(viewUpdateSource.onNodeRemoved(callback));
}
if (dependencies.includes('connections')) {
unsubscribes.push(viewUpdateSource.onConnectionAdded(callback));
unsubscribes.push(viewUpdateSource.onConnectionRemoved(callback));
}
if (dependencies.includes('runtime')) {
unsubscribes.push(viewUpdateSource.onRuntimeEvent(callback));
}
return () => unsubscribes.forEach(unsub => unsub());
}, [dependencies, callback]);
}
```
#### Update Strategy Per View
| View | Update Triggers | Debounce? |
|------|-----------------|-----------|
| Topology Map | Component added/removed | Yes (500ms) |
| Component X-Ray | Nodes/connections in current component | Yes (200ms) |
| Trigger Chain | Runtime events (when recording) | No (real-time) |
| Node Census | Nodes/connections changed | Yes (300ms) |
| Data Lineage | Connections changed, runtime values | Yes (200ms) |
| Impact Radar | Component interface changed | Yes (500ms) |
| Semantic Layers | Nodes added/removed | Yes (100ms) |
### 5. Understanding Existing Debug Infrastructure (CRITICAL)
**Several views need to integrate with Noodl's existing runtime debugging.** Before building those views, we need to document how the current system works.
The existing canvas already has powerful runtime features:
- Nodes "light up" when they fire
- Connections animate when data flows
- DebugInspector shows live values on hover
- You can pin inspectors to track values over time
#### Key Components to Investigate
```typescript
// These likely exist and need documentation:
// 1. DebugInspector - manages live value inspection
// Location: packages/noodl-editor/src/editor/src/models/DebugInspector/
DebugInspector.instance.getValueForPort(nodeId, port);
DebugInspector.InspectorsModel; // Manages pinned inspectors
// 2. Node highlighting in canvas
// Location: packages/noodl-editor/src/editor/src/views/nodegrapheditor/
nodeGraphEditor.highlightNode(node, duration);
nodeGraphEditor.highlightConnection(connection, duration);
// 3. Runtime event emission
// Location: packages/noodl-runtime/src/
nodeInstance.on('outputChanged', handler);
nodeInstance.on('signalSent', handler);
```
#### Documentation Task
Before implementing VIEW-003 (Trigger Chain) or live features in VIEW-005 (Data Lineage), add a research phase:
1. **Map the debug event flow**: How do runtime events get from node execution to canvas highlighting?
2. **Document DebugInspector API**: What methods are available? How does pinning work?
3. **Identify extension points**: Where can we tap in to record events?
4. **Find component boundary handling**: How does debugging work across nested components?
This research will be invaluable for:
- VIEW-003: Trigger Chain Debugger (needs to record all debug events)
- VIEW-005: Data Lineage live mode (needs live value access)
- VIEW-007: Semantic Layers (needs to preserve highlighting behavior)
---
## Technical Design
### 1. Graph Analysis Module
**Location:** `packages/noodl-editor/src/editor/src/utils/graphAnalysis/`
```typescript
// index.ts - public API
export * from './traversal';
export * from './crossComponent';
export * from './categorization';
export * from './duplicateDetection';
export * from './types';
```
#### 1.1 Traversal Utilities
```typescript
// traversal.ts
export interface ConnectionPath {
node: NodeGraphNode;
port: string;
direction: 'input' | 'output';
connection?: Connection;
}
export interface TraversalResult {
path: ConnectionPath[];
crossedComponents: ComponentCrossing[];
terminatedAt: 'source' | 'sink' | 'cycle' | 'component-boundary';
}
/**
* Trace a connection chain from a starting point.
* Follows connections upstream (to sources) or downstream (to sinks).
*/
export function traceConnectionChain(
component: ComponentModel,
startNodeId: string,
startPort: string,
direction: 'upstream' | 'downstream',
options?: {
maxDepth?: number;
crossComponents?: boolean;
stopAtTypes?: string[]; // Stop when hitting these node types
}
): TraversalResult;
/**
* Get all nodes directly connected to a given node.
*/
export function getConnectedNodes(
component: ComponentModel,
nodeId: string
): { inputs: NodeGraphNode[]; outputs: NodeGraphNode[] };
/**
* Get all connections for a specific port.
*/
export function getPortConnections(
component: ComponentModel,
nodeId: string,
portName: string,
direction: 'input' | 'output'
): Connection[];
/**
* Build adjacency list representation of the graph.
*/
export function buildAdjacencyList(
component: ComponentModel
): Map<string, { inputs: string[]; outputs: string[] }>;
```
#### 1.2 Cross-Component Resolution
```typescript
// crossComponent.ts
export interface ComponentCrossing {
fromComponent: ComponentModel;
toComponent: ComponentModel;
viaPort: string;
direction: 'into' | 'outof';
}
export interface ComponentUsage {
component: ComponentModel; // The component being used
usedIn: ComponentModel; // Where it's used
instanceNodeId: string; // The node ID of the instance
connectedPorts: {
port: string;
connectedTo: { nodeId: string; port: string }[];
}[];
}
/**
* Find all places where a component is instantiated across the project.
*/
export function findComponentUsages(
project: ProjectModel,
componentName: string
): ComponentUsage[];
/**
* Resolve a Component Input/Output to its external connections.
* Given a Component Inputs node, find what feeds into it from the parent.
*/
export function resolveComponentBoundary(
project: ProjectModel,
component: ComponentModel,
boundaryNodeId: string, // Component Inputs or Component Outputs node
portName: string
): ExternalConnection[];
/**
* Build complete component dependency graph for the project.
*/
export function buildComponentDependencyGraph(
project: ProjectModel
): {
nodes: ComponentModel[];
edges: { from: string; to: string; count: number }[];
};
```
#### 1.3 Node Categorization
```typescript
// categorization.ts
export type NodeCategory =
| 'visual' // Groups, Text, Image, etc.
| 'data' // Variables, Objects, Arrays
| 'logic' // Conditions, Expressions, Switches
| 'events' // Send Event, Receive Event, Component I/O
| 'api' // REST, Function, Cloud Functions
| 'navigation' // Page Router, Navigate
| 'animation' // Transitions, States (animation-related)
| 'utility' // Other/misc
export interface CategorizedNodes {
byCategory: Map<NodeCategory, NodeGraphNode[]>;
byType: Map<string, NodeGraphNode[]>;
totals: { category: NodeCategory; count: number }[];
}
/**
* Categorize all nodes in a component by semantic type.
*/
export function categorizeNodes(component: ComponentModel): CategorizedNodes;
/**
* Get the category for a specific node type.
*/
export function getNodeCategory(nodeType: string): NodeCategory;
/**
* Check if a node is a visual node (has visual hierarchy).
*/
export function isVisualNode(node: NodeGraphNode): boolean;
/**
* Check if a node is a data source (Variable, Object, Array, etc.).
*/
export function isDataSourceNode(node: NodeGraphNode): boolean;
```
#### 1.4 Duplicate Detection
```typescript
// duplicateDetection.ts
export interface DuplicateGroup {
name: string;
type: string;
instances: {
node: NodeGraphNode;
component: ComponentModel;
connectionCount: number;
}[];
severity: 'info' | 'warning' | 'error';
reason: string; // Why this might be a problem
}
/**
* Find potential duplicate nodes within a component.
* Duplicates = same type + same/similar name.
*/
export function findDuplicatesInComponent(
component: ComponentModel
): DuplicateGroup[];
/**
* Find potential duplicate nodes across the entire project.
*/
export function findDuplicatesInProject(
project: ProjectModel
): DuplicateGroup[];
/**
* Analyze if duplicates might cause conflicts.
* E.g., two Variables with same name writing to same output.
*/
export function analyzeDuplicateConflicts(
duplicates: DuplicateGroup[]
): ConflictAnalysis[];
```
### 2. View Panel Framework
**Location:** `packages/noodl-editor/src/editor/src/views/AnalysisPanel/`
```typescript
// AnalysisPanel.tsx
export interface AnalysisView {
id: string;
name: string;
icon: IconName;
component: React.ComponentType<AnalysisViewProps>;
}
export interface AnalysisViewProps {
project: ProjectModel;
currentComponent: ComponentModel | null;
selectedNodes: NodeGraphNode[];
onNavigateToNode: (componentName: string, nodeId: string) => void;
onNavigateToComponent: (componentName: string) => void;
}
export function AnalysisPanel({
views,
activeViewId,
onViewChange
}: AnalysisPanelProps): JSX.Element;
```
#### Panel Structure
```
┌─────────────────────────────────────────────────────────────┐
│ Analysis [×] │
├─────────────────────────────────────────────────────────────┤
│ [🗺️] [📋] [🔍] [📊] [🔗] [💥] [📑] │
│ Map Census Find Lin Imp Layers │
├─────────────────────────────────────────────────────────────┤
│ │
│ {Active View Component} │
│ │
├─────────────────────────────────────────────────────────────┤
│ 📍 Current: Presales Page [↗ Canvas] │
└─────────────────────────────────────────────────────────────┘
```
### 2. View Infrastructure
**Location:** `packages/noodl-editor/src/editor/src/views/`
The visualization system has three types of components:
#### 2a. Meta View Tabs
These replace the canvas entirely (like Topology Map, Trigger Chain):
```typescript
// MetaViewTabs.tsx - Tab bar for switching between Canvas and meta views
export type MetaViewId = 'canvas' | 'topology' | 'triggers';
export interface MetaViewDefinition {
id: MetaViewId;
name: string;
icon: IconName;
shortcut?: string;
component: React.ComponentType<MetaViewProps>;
}
export interface MetaViewProps {
project: ProjectModel;
onNavigateToCanvas: (componentName: string, nodeId?: string) => void;
}
```
```
┌─────────────────────────────────────────────────────────────────┐
│ ◀ ▶ │ [🗺️ Canvas] [📊 Topology] [⚡ Triggers] │ ▶ Preview │
└─────────────────────────────────────────────────────────────────┘
```
#### 2b. Sidebar Panels
These open alongside the canvas (using existing SidebarModel):
```typescript
// Already supported by SidebarModel!
// Just register new React components:
SidebarModel.instance.register({
id: 'xray',
name: 'X-Ray',
icon: IconName.Search,
panel: ComponentXRayPanel // React component
});
SidebarModel.instance.register({
id: 'census',
name: 'Census',
icon: IconName.List,
panel: NodeCensusPanel // React component
});
```
#### 2c. Canvas Overlays (THE BIG ONE)
These enhance the existing canvas with toggleable overlays:
```typescript
// CanvasOverlayManager.tsx
export type OverlayId = 'layers' | 'lineage' | 'impact';
export interface CanvasOverlay {
id: OverlayId;
name: string;
icon: IconName;
// The React component that renders over the canvas
component: React.ComponentType<CanvasOverlayProps>;
// Optional panel component for controls/details
panelComponent?: React.ComponentType<OverlayPanelProps>;
}
export interface CanvasOverlayProps {
// Canvas transform info (for positioning overlays)
scale: number;
pan: { x: number; y: number };
// Current component context
component: ComponentModel;
// Callback to control canvas highlighting
highlightAPI: CanvasHighlightAPI;
}
export interface CanvasOverlayManagerProps {
activeOverlays: Set<OverlayId>;
onToggleOverlay: (id: OverlayId) => void;
}
// Toolbar for toggling overlays
export function CanvasOverlayToolbar({
activeOverlays,
onToggleOverlay
}: CanvasOverlayManagerProps): JSX.Element;
```
**Based on CommentLayer pattern** - this already exists and works!
```
┌─────────────────────────────────────────────────────────────────┐
│ Login Page [Layers] [Lineage ✓] [Impact]│
├─────────────────────────────────────────────────────────────────┤
│ │
│ Canvas with overlays rendered on top... │
│ Lineage highlights glowing on connections... │
│ │
├─────────────────────────────────────────────────────────────────┤
│ 🔗 Lineage Panel (when active) [×] │
│ Details about the current lineage trace... │
└─────────────────────────────────────────────────────────────────┘
```
### 3. Canvas Highlighting API (Critical for Overlays)
**This is what makes the overlays legendary!**
The canvas needs an API for persistent, multi-channel highlighting:
```typescript
// canvasHighlight.ts
export interface CanvasHighlightAPI {
// Create a new highlight (returns handle to control it)
highlightNodes(nodeIds: string[], options: HighlightOptions): HighlightHandle;
highlightConnections(connections: ConnectionRef[], options: HighlightOptions): HighlightHandle;
highlightPath(path: PathDefinition, options: HighlightOptions): HighlightHandle;
// Query active highlights
getActiveHighlights(): HighlightInfo[];
// Clear all highlights in a channel
clearChannel(channel: string): void;
// Clear everything
clearAll(): void;
}
export interface HighlightOptions {
channel: string; // 'lineage', 'impact', 'selection', etc.
color?: string; // Override default channel color
style?: 'solid' | 'pulse' | 'glow';
persistent?: boolean; // Stay until explicitly dismissed (default: true)
label?: string; // Optional label shown near highlight
}
export interface HighlightHandle {
id: string;
channel: string;
update(nodeIds: string[]): void; // Change what's highlighted
dismiss(): void; // Remove this highlight
}
export interface PathDefinition {
nodes: string[]; // Ordered list of node IDs in the path
connections: ConnectionRef[]; // Connections between them
crossesComponents?: boolean; // True if path spans multiple components
}
// Channel defaults
const HIGHLIGHT_CHANNELS = {
lineage: { color: '#4A90D9', style: 'glow' }, // Blue glow for data flow
impact: { color: '#F5A623', style: 'pulse' }, // Orange pulse for dependencies
selection: { color: '#FFFFFF', style: 'solid' }, // White for selection
warning: { color: '#FF6B6B', style: 'pulse' } // Red pulse for issues
};
```
#### Key Behavior: Persistent Across Navigation!
When you navigate from Presales Page to its parent App Shell, the lineage highlights should:
1. Stay visible on any nodes that exist in the new view
2. Show an indicator: "⬆️ Path continues in child: Presales Page [Go ↗]"
```typescript
// Highlight state is stored globally, not per-component
interface GlobalHighlightState {
activeHighlights: Map<string, {
handle: HighlightHandle;
affectedComponents: string[]; // Which components have nodes in this highlight
currentlyVisible: string[]; // Node IDs visible in current component
}>;
}
```
#### Navigation Helpers
```typescript
// navigation.ts
/**
* Navigate to a specific node on the canvas.
* - Switches to the correct component if needed
* - Pans the canvas to center on the node
* - Selects the node
* - Does NOT dismiss active highlights (they persist!)
*/
export function navigateToNode(
componentName: string,
nodeId: string,
options?: {
select?: boolean;
highlight?: boolean; // Add temporary highlight on top of persistent ones
zoomToFit?: boolean;
}
): void;
/**
* Navigate to a component (open it in the canvas).
* Active highlights update to show relevant portions.
*/
export function navigateToComponent(
componentName: string
): void;
```
### 4. Shared Types
```typescript
// types.ts
export interface NodeSummary {
id: string;
type: string;
displayName: string;
label: string | null; // User-assigned label
category: NodeCategory;
inputCount: number;
outputCount: number;
connectedInputs: number;
connectedOutputs: number;
hasChildren: boolean; // For visual nodes
childCount: number;
}
export interface ConnectionSummary {
fromNode: NodeSummary;
fromPort: string;
toNode: NodeSummary;
toPort: string;
}
export interface ComponentSummary {
name: string;
fullName: string;
nodeCount: number;
connectionCount: number;
inputPorts: string[];
outputPorts: string[];
usedComponents: string[]; // Subcomponents used
usedByComponents: string[]; // Components that use this one
categories: { category: NodeCategory; count: number }[];
}
```
---
## Implementation Phases
### Phase 1: Core Traversal (1 day)
1. Create `graphAnalysis/` folder structure
2. Implement `traversal.ts`:
- `traceConnectionChain()`
- `getConnectedNodes()`
- `getPortConnections()`
- `buildAdjacencyList()`
3. Write unit tests for traversal functions
4. Test with real component data
**Verification:**
- [ ] Can trace a connection chain forward and backward
- [ ] Correctly handles branching (one output to multiple inputs)
- [ ] Stops at specified depth limits
- [ ] Handles cycles without infinite loops
### Phase 2: Cross-Component Resolution (1 day)
1. Implement `crossComponent.ts`:
- `findComponentUsages()`
- `resolveComponentBoundary()`
- `buildComponentDependencyGraph()`
2. Handle Component Inputs/Outputs nodes specially
3. Test with nested component scenarios
**Verification:**
- [ ] Can find all places a component is used
- [ ] Can resolve what feeds into a Component Input from the parent
- [ ] Dependency graph correctly shows component relationships
### Phase 3: Categorization & Duplicate Detection (0.5 days)
1. Implement `categorization.ts`
2. Implement `duplicateDetection.ts`
3. Create category mapping for all known node types
**Verification:**
- [ ] All node types correctly categorized
- [ ] Duplicates detected based on name + type
- [ ] Severity levels assigned appropriately
### Phase 4: View Switcher Framework (1 day)
1. Create `ViewSwitcher/` component structure
2. Implement dropdown UI with view list
3. Wire up view switching (replace canvas area content)
4. Implement state persistence (localStorage)
5. Add keyboard shortcuts for view switching
6. Integrate with existing canvas header
**Verification:**
- [ ] Dropdown appears in canvas header
- [ ] Clicking view switches content area
- [ ] State persists across page reloads
- [ ] Keyboard shortcuts work
- [ ] "Node Canvas" view shows existing canvas (no regression)
### Phase 5: Navigation Helpers (0.5 days)
1. Implement `navigateToCanvas()` with all options
2. Implement `highlightNodes()` / `highlightConnectionPath()`
3. Integrate with NodeGraphEditor
4. Test jumping from placeholder views
**Verification:**
- [ ] Clicking a node reference in any view jumps to canvas
- [ ] Node is centered and selected
- [ ] Temporary highlight works for tracing
- [ ] Works across component boundaries
### Phase 6: Debug Infrastructure Documentation (0.5 days)
1. Research and document existing DebugInspector system
2. Document node highlighting mechanism
3. Document runtime event emission
4. Identify extension points for VIEW-003 and VIEW-005
5. Create `DEBUG-INFRASTRUCTURE.md` reference doc
**Verification:**
- [ ] DebugInspector API documented
- [ ] Node highlighting mechanism understood
- [ ] Clear path for Trigger Chain integration
---
## Files to Create
```
packages/noodl-editor/src/editor/src/
├── utils/
│ └── graphAnalysis/
│ ├── index.ts
│ ├── traversal.ts
│ ├── crossComponent.ts
│ ├── categorization.ts
│ ├── duplicateDetection.ts
│ ├── navigation.ts
│ └── types.ts
├── views/
│ ├── ViewSwitcher/
│ │ ├── index.ts
│ │ ├── ViewSwitcher.tsx
│ │ ├── ViewSwitcher.module.scss
│ │ ├── ViewDropdown.tsx
│ │ ├── viewDefinitions.ts
│ │ └── viewStateStore.ts
│ └── AnalysisViews/
│ ├── index.ts
│ ├── shared/
│ │ ├── ViewContainer.tsx # Common wrapper for all views
│ │ ├── NodeReference.tsx # Clickable node link component
│ │ └── ComponentBadge.tsx # Component name badge with navigation
│ └── [individual view folders created by VIEW-001 through VIEW-007]
└── docs/
└── DEBUG-INFRASTRUCTURE.md # Documentation of existing debug system
```
---
## Testing Strategy
### Unit Tests
```typescript
// __tests__/traversal.test.ts
describe('traceConnectionChain', () => {
it('follows a simple linear chain', () => { ... });
it('handles branching outputs', () => { ... });
it('stops at max depth', () => { ... });
it('detects and handles cycles', () => { ... });
it('crosses component boundaries when enabled', () => { ... });
});
```
### Integration Tests
- Load a real complex project
- Run traversal from various starting points
- Verify results match manual inspection
---
## Success Criteria
- [ ] All traversal functions work correctly on complex graphs
- [ ] Cross-component resolution handles nested components
- [ ] View panel integrates cleanly with existing sidebar
- [ ] Navigation to canvas works from external code
- [ ] Types are comprehensive and well-documented
- [ ] Unit tests cover edge cases
---
## Risks & Mitigations
| Risk | Mitigation |
|------|------------|
| Performance on large projects | Add pagination/lazy loading, cache results |
| Missing node type categorizations | Start with common types, add others as discovered |
| Complex component nesting | Test with deeply nested scenarios early |
---
## Dependencies
- None (this is the foundation)
## Blocks
- VIEW-001 through VIEW-007 (all visualization views)

View File

@@ -0,0 +1,363 @@
# VIEW-001: Project Topology Map
**View Type:** 🗺️ Meta View (replaces canvas with project-wide view)
## Overview
A high-level "Google Maps" view of the entire project's component structure. Shows how components relate to each other as a navigable graph, with a "You Are Here" indicator and the ability to jump to any component.
**Estimate:** 4-5 days
**Priority:** HIGH
**Complexity:** Medium
**Dependencies:** VIEW-000 (Foundation)
---
## The Problem
When working in a complex Noodl project:
- You can't see how many components exist or how they relate
- You lose track of where you are when deep in nested components
- There's no way to get a "big picture" view of the project architecture
- Finding a component means hunting through the Components Panel tree
---
## The Solution
A visual map showing:
- Every component as a box/node
- Arrows showing which components use which others
- "You Are Here" highlight on the current component
- Click-to-navigate to any component
- Breadcrumb trail showing how you got to your current location
---
## User Stories
1. **As a new developer** joining a project, I want to see the overall structure so I can understand how the app is organized.
2. **As a developer debugging**, I want to see where the current component fits in the hierarchy so I understand the context.
3. **As a developer navigating**, I want to click on any component in the map to jump directly to it.
4. **As a developer planning**, I want to see which components are reused in multiple places.
---
## UI Design
### Main View
```
┌─────────────────────────────────────────────────────────────────┐
│ Project Topology [Fit] [][+]│
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Login │ │ Home │ │ Profile │ 📄 PAGES │
│ │ Page │ │ Page │ │ Page │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │
│ │ ┌──────────┼───────────────┤ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────┐ ┌──────────────┐ │
│ │ AuthFlow │ │ NavBar │ │ UserCard │ 🧩 SHARED │
│ │ ★ YOU ARE │ │ (×3) │ │ (×2) │ │
│ │ HERE │ └─────────┘ └──────────────┘ │
│ └──────┬──────┘ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ LoginForm │ │ AvatarIcon │ 🔧 NESTED │
│ │ Component │ │ Component │ │
│ └─────────────┘ └──────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ 📍 Path: App Shell → Login Page → AuthFlow │
│ 📊 23 components total | 8 pages | 12 shared | 3 nested │
└─────────────────────────────────────────────────────────────────┘
```
### Component Node Styling
| Component Type | Visual Style |
| ------------------------ | ----------------------------------- |
| Page | Larger box, 📄 icon, distinct color |
| Shared (used 2+ times) | Badge showing usage count (×3) |
| Current ("You Are Here") | Highlighted border, ★ indicator |
| Has subcomponents | Subtle "expandable" indicator |
| Orphan (unused) | Dimmed, warning style |
### Interactions
- **Click component** → Jump to that component in canvas
- **Hover component** → Show tooltip with summary (node count, I/O ports)
- **Double-click** → Expand to show internal structure (optional)
- **Drag to pan** → Navigate large maps
- **Scroll to zoom** → Zoom in/out
- **[Fit] button** → Fit entire map in view
- **Click breadcrumb** → Navigate to that ancestor component
---
## Technical Design
### Data Model
```typescript
interface TopologyNode {
component: ComponentModel;
name: string;
fullName: string;
type: 'page' | 'component';
usageCount: number; // How many places use this component
usedBy: string[]; // Which components use this one
uses: string[]; // Which components this one uses
depth: number; // Nesting depth from root
isCurrentComponent: boolean;
}
interface TopologyEdge {
from: string; // Component fullName
to: string; // Component fullName
count: number; // How many instances
}
interface TopologyGraph {
nodes: TopologyNode[];
edges: TopologyEdge[];
currentPath: string[]; // Breadcrumb path
}
```
### Building the Graph
```typescript
function buildTopologyGraph(project: ProjectModel): TopologyGraph {
const nodes: TopologyNode[] = [];
const edges: TopologyEdge[] = [];
// 1. Collect all components
project.forEachComponent((component) => {
nodes.push({
component,
name: component.name,
fullName: component.fullName,
type: isPageComponent(component) ? 'page' : 'component',
usageCount: 0,
usedBy: [],
uses: [],
depth: 0,
isCurrentComponent: false
});
});
// 2. Find all component usages (nodes of type that matches a component name)
nodes.forEach((node) => {
node.component.graph.forEachNode((graphNode) => {
if (isComponentInstance(graphNode)) {
const usedComponentName = graphNode.type.name;
const usedNode = nodes.find((n) => n.fullName === usedComponentName);
if (usedNode) {
usedNode.usageCount++;
usedNode.usedBy.push(node.fullName);
node.uses.push(usedComponentName);
edges.push({
from: node.fullName,
to: usedComponentName,
count: 1 // Aggregate later
});
}
}
});
});
// 3. Calculate depths (BFS from pages)
calculateDepths(nodes, edges);
return { nodes, edges, currentPath: [] };
}
```
### Layout Algorithm
Use a hierarchical layout (like Dagre) to position nodes:
```typescript
import dagre from 'dagre';
function layoutTopology(graph: TopologyGraph): PositionedTopologyGraph {
const g = new dagre.graphlib.Graph();
g.setGraph({ rankdir: 'TB', ranksep: 80, nodesep: 50 });
g.setDefaultEdgeLabel(() => ({}));
// Add nodes with estimated sizes
graph.nodes.forEach((node) => {
const width = node.type === 'page' ? 140 : 120;
const height = 60;
g.setNode(node.fullName, { width, height });
});
// Add edges
graph.edges.forEach((edge) => {
g.setEdge(edge.from, edge.to);
});
dagre.layout(g);
// Extract positions
return graph.nodes.map((node) => ({
...node,
x: g.node(node.fullName).x,
y: g.node(node.fullName).y
}));
}
```
### Rendering
Could use:
- **SVG** - Simple, good for moderate sizes
- **Canvas** - Better performance for large graphs
- **React Flow** - Library specifically for this (already used elsewhere?)
Recommend starting with SVG for simplicity, refactor to Canvas if performance issues.
---
## Implementation Phases
### Phase 1: Data Collection (1 day)
1. Implement `buildTopologyGraph()` using VIEW-000 utilities
2. Correctly identify component instances vs regular nodes
3. Calculate usage counts and relationships
4. Determine page vs component classification
**Verification:**
- [ ] All components in project appear in graph
- [ ] Component usage relationships are correct
- [ ] Pages correctly identified
### Phase 2: Layout Algorithm (1 day)
1. Integrate Dagre.js for hierarchical layout
2. Position pages at top, shared components in middle, nested at bottom
3. Handle edge cases (cycles, orphans)
4. Calculate viewport bounds
**Verification:**
- [ ] Layout produces non-overlapping nodes
- [ ] Hierarchy is visually clear
- [ ] Large graphs don't break
### Phase 3: Basic Rendering (1 day)
1. Create `TopologyMapView` React component
2. Render nodes as styled boxes
3. Render edges as lines/arrows
4. Implement pan and zoom
5. Add "Fit to View" button
**Verification:**
- [ ] Graph renders correctly
- [ ] Can pan and zoom
- [ ] Fit button works
### Phase 4: Interactivity (1 day)
1. Click node → Navigate to component
2. Hover → Show tooltip
3. Highlight current component
4. Show breadcrumb path
5. Add "You Are Here" indicator
**Verification:**
- [ ] Clicking navigates correctly
- [ ] Current component highlighted
- [ ] Breadcrumb shows path
### Phase 5: Polish (0.5-1 day)
1. Style refinement (colors, icons, badges)
2. Add usage count badges
3. Add orphan warnings
4. Performance optimization if needed
5. Add to Analysis Panel tabs
**Verification:**
- [ ] Visually polished
- [ ] Integrates with Analysis Panel
- [ ] Performance acceptable on large projects
---
## Files to Create
```
packages/noodl-editor/src/editor/src/views/AnalysisPanel/
└── TopologyMapView/
├── index.ts
├── TopologyMapView.tsx
├── TopologyMapView.module.scss
├── TopologyNode.tsx
├── TopologyEdge.tsx
├── TopologyTooltip.tsx
├── Breadcrumbs.tsx
└── useTopologyGraph.ts
```
---
## Success Criteria
- [ ] Shows all components in the project
- [ ] Correctly displays which components use which
- [ ] "You Are Here" correctly highlights current component
- [ ] Click navigation works
- [ ] Breadcrumb trail is accurate
- [ ] Renders reasonably fast (< 1s) for projects with 50+ components
- [ ] Layout is readable (no major overlaps)
---
## Future Enhancements
- **Expand/collapse** - Click to expand a component and show its internal node summary
- **Filter** - Show only pages, only shared, only orphans
- **Search** - Find and highlight a component by name
- **Minimap** - Small overview when zoomed in
- **Export** - Export as PNG/SVG for documentation
---
## Risks & Mitigations
| Risk | Mitigation |
| ------------------------------------ | ----------------------------------------- |
| Large projects (100+ components) | Virtual rendering, progressive loading |
| Complex nesting causes layout issues | Test with deeply nested projects early |
| Dagre.js performance | Consider WebWorker for layout calculation |
---
## Dependencies
- VIEW-000 Foundation (for `buildComponentDependencyGraph`)
- Dagre.js (layout library)
## Blocks
- None (independent view)

View File

@@ -0,0 +1,423 @@
# VIEW-002: Component X-Ray
**View Type:** 📋 Sidebar Panel (opens alongside canvas)
## Overview
A summary card view that shows everything important about a component at a glance: what it does, what goes in and out, what it contains, where it's used, and what external calls it makes. Think of it as the "component profile page."
**Estimate:** 2-3 days
**Priority:** HIGH
**Complexity:** Low
**Dependencies:** VIEW-000 (Foundation)
---
## The Problem
To understand a component today, you have to:
1. Open it in the canvas
2. Scroll around to see all nodes
3. Mentally categorize what's there
4. Check the Component Inputs/Outputs nodes to understand the interface
5. Hunt for REST/Function calls scattered around
6. Go back to the Components Panel to see if it's used elsewhere
There's no quick "tell me about this component" view.
---
## The Solution
A single-screen summary that answers:
- **What does this component do?** (Node breakdown by category)
- **What's the interface?** (Inputs and outputs)
- **What's inside?** (Subcomponents used)
- **Where is it used?** (Parent components)
- **What external things does it touch?** (REST, Functions, Events)
---
## User Stories
1. **As a developer reviewing code**, I want to quickly understand what a component does without diving into the canvas.
2. **As a developer debugging**, I want to see all external dependencies (API calls, events) in one place.
3. **As a developer refactoring**, I want to know everywhere this component is used before I change it.
4. **As a new team member**, I want to understand component interfaces (inputs/outputs) without reading the implementation.
---
## UI Design
### X-Ray Card View
```
┌─────────────────────────────────────────────────────────────────┐
│ Component X-Ray [Open →] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 🧩 AuthFlow │ │
│ │ /Components/Auth/AuthFlow │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ USED IN (3 places) ────────────────────────────────────────┐ │
│ │ 📄 Login Page [→ Open] │ │
│ │ 📄 Settings Page [→ Open] │ │
│ │ 📄 App Shell [→ Open] │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ INTERFACE ─────────────────────────────────────────────────┐ │
│ │ INPUTS │ OUTPUTS │ │
│ │ ──────── │ ───────── │ │
│ │ → onLoginRequest (signal) │ currentUser (object) → │ │
│ │ → redirectUrl (string) │ authError (string) → │ │
│ │ → initialMode (string) │ isAuthenticated (boolean) → │ │
│ │ │ onSuccess (signal) → │ │
│ │ │ onFailure (signal) → │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ CONTAINS ──────────────────────────────────────────────────┐ │
│ │ SUBCOMPONENTS │ │
│ │ └─ LoginForm (component) [→ X-Ray] │ │
│ │ └─ SignupForm (component) [→ X-Ray] │ │
│ │ └─ ForgotPassword (component) [→ X-Ray] │ │
│ │ │ │
│ │ NODE BREAKDOWN │ │
│ │ ├─ 📦 Visual 12 nodes (Groups, Text, Images) │ │
│ │ ├─ 💾 Data 5 nodes (Variables, Objects) │ │
│ │ ├─ ⚡ Logic 8 nodes (Conditions, Expressions) │ │
│ │ ├─ 📡 Events 3 nodes (Send/Receive Event) │ │
│ │ └─ 🔧 Other 2 nodes │ │
│ │ ───── │ │
│ │ 30 total │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ EXTERNAL DEPENDENCIES ─────────────────────────────────────┐ │
│ │ 🌐 REST Calls │ │
│ │ POST /api/auth/login [→ Find] │ │
│ │ POST /api/auth/signup [→ Find] │ │
│ │ POST /api/auth/reset-password [→ Find] │ │
│ │ │ │
│ │ 📨 Events Sent │ │
│ │ "auth:success" [→ Find receivers] │ │
│ │ "auth:failure" [→ Find receivers] │ │
│ │ │ │
│ │ 📩 Events Received │ │
│ │ "app:logout" [→ Find senders] │ │
│ │ │ │
│ │ 🔧 Functions │ │
│ │ validateEmail [→ Find] │ │
│ │ hashPassword [→ Find] │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ INTERNAL STATE ────────────────────────────────────────────┐ │
│ │ Variables: authMode, errorMessage, isLoading │ │
│ │ Objects: pendingUser, formData │ │
│ │ States: formState (login|signup|reset), loadingState │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Collapsed View (for quick scanning)
```
┌─────────────────────────────────────────────────────────────────┐
│ 🧩 AuthFlow Used in: 3 | Nodes: 30 | APIs: 3 │
│ ↓ 3 inputs ↑ 5 outputs Contains: LoginForm +2 │
└─────────────────────────────────────────────────────────────────┘
```
### Interactions
- **[Open →]** - Jump to this component in the canvas
- **[→ X-Ray]** - Show X-Ray for that subcomponent
- **[→ Find]** - Jump to that node in the canvas
- **[→ Find receivers/senders]** - Show all nodes that receive/send that event
- **Click "USED IN" item** - Jump to the parent component
- **Expand/Collapse sections** - Toggle visibility of sections
---
## Technical Design
### Data Model
```typescript
interface ComponentXRay {
// Identity
name: string;
fullName: string;
path: string; // Folder path
// Usage
usedIn: {
component: ComponentModel;
instanceCount: number;
}[];
// Interface
inputs: {
name: string;
type: string;
isSignal: boolean;
}[];
outputs: {
name: string;
type: string;
isSignal: boolean;
}[];
// Contents
subcomponents: {
name: string;
component: ComponentModel;
}[];
nodeBreakdown: {
category: NodeCategory;
count: number;
nodeTypes: { type: string; count: number }[];
}[];
totalNodes: number;
// External dependencies
restCalls: {
method: string;
endpoint: string;
nodeId: string;
}[];
eventsSent: {
eventName: string;
nodeId: string;
}[];
eventsReceived: {
eventName: string;
nodeId: string;
}[];
functionCalls: {
functionName: string;
nodeId: string;
}[];
// Internal state
variables: { name: string; nodeId: string }[];
objects: { name: string; nodeId: string }[];
statesNodes: {
name: string;
nodeId: string;
states: string[];
}[];
}
```
### Building X-Ray Data
```typescript
function buildComponentXRay(
project: ProjectModel,
component: ComponentModel
): ComponentXRay {
const xray: ComponentXRay = {
name: component.name,
fullName: component.fullName,
path: getComponentPath(component),
usedIn: findComponentUsages(project, component.fullName),
inputs: getComponentInputs(component),
outputs: getComponentOutputs(component),
subcomponents: [],
nodeBreakdown: [],
totalNodes: 0,
restCalls: [],
eventsSent: [],
eventsReceived: [],
functionCalls: [],
variables: [],
objects: [],
statesNodes: []
};
// Analyze all nodes in the component
component.graph.forEachNode((node) => {
xray.totalNodes++;
// Check for subcomponents
if (isComponentInstance(node)) {
xray.subcomponents.push({
name: node.type.name,
component: findComponent(project, node.type.name)
});
}
// Check for REST calls
if (node.type.name === 'REST' || node.type.name.includes('REST')) {
xray.restCalls.push({
method: node.parameters.method || 'GET',
endpoint: node.parameters.endpoint || node.parameters.url,
nodeId: node.id
});
}
// Check for events
if (node.type.name === 'Send Event') {
xray.eventsSent.push({
eventName: node.parameters.eventName || node.parameters.channel,
nodeId: node.id
});
}
if (node.type.name === 'Receive Event') {
xray.eventsReceived.push({
eventName: node.parameters.eventName || node.parameters.channel,
nodeId: node.id
});
}
// Check for functions
if (node.type.name === 'Function' || node.type.name === 'Javascript') {
xray.functionCalls.push({
functionName: node.label || node.parameters.name || 'Anonymous',
nodeId: node.id
});
}
// Check for state nodes
if (node.type.name === 'Variable') {
xray.variables.push({ name: node.label || 'Unnamed', nodeId: node.id });
}
if (node.type.name === 'Object') {
xray.objects.push({ name: node.label || 'Unnamed', nodeId: node.id });
}
if (node.type.name === 'States') {
xray.statesNodes.push({
name: node.label || 'Unnamed',
nodeId: node.id,
states: extractStatesFromNode(node)
});
}
});
// Build category breakdown
xray.nodeBreakdown = buildCategoryBreakdown(component);
return xray;
}
```
---
## Implementation Phases
### Phase 1: Data Collection (0.5-1 day)
1. Implement `buildComponentXRay()` function
2. Extract inputs/outputs from Component Inputs/Outputs nodes
3. Detect subcomponent usages
4. Find REST, Event, Function nodes
5. Find state-related nodes (Variables, Objects, States)
**Verification:**
- [ ] All sections populated correctly for test component
- [ ] Subcomponent detection works
- [ ] External dependencies found
### Phase 2: Basic UI (1 day)
1. Create `ComponentXRayView` React component
2. Implement collapsible sections
3. Style the card layout
4. Add icons for categories
**Verification:**
- [ ] All sections render correctly
- [ ] Sections expand/collapse
- [ ] Looks clean and readable
### Phase 3: Interactivity (0.5-1 day)
1. Implement "Open in Canvas" navigation
2. Implement "Find Node" navigation
3. Implement "Show X-Ray" for subcomponents
4. Add "Find receivers/senders" for events
5. Wire up to Analysis Panel context
**Verification:**
- [ ] All navigation links work
- [ ] Can drill into subcomponents
- [ ] Event tracking works
### Phase 4: Polish (0.5 day)
1. Add collapsed summary view
2. Improve typography and spacing
3. Add empty state handling
4. Performance optimization
**Verification:**
- [ ] Collapsed view useful
- [ ] Empty sections handled gracefully
- [ ] Renders quickly
---
## Files to Create
```
packages/noodl-editor/src/editor/src/views/AnalysisPanel/
└── ComponentXRayView/
├── index.ts
├── ComponentXRayView.tsx
├── ComponentXRayView.module.scss
├── XRaySection.tsx
├── InterfaceSection.tsx
├── ContentsSection.tsx
├── DependenciesSection.tsx
├── StateSection.tsx
└── useComponentXRay.ts
```
---
## Success Criteria
- [ ] Shows accurate usage count
- [ ] Shows correct inputs/outputs with types
- [ ] Lists all subcomponents
- [ ] Finds all REST calls with endpoints
- [ ] Finds all Send/Receive Events
- [ ] Finds all Function nodes
- [ ] Node breakdown by category is accurate
- [ ] All navigation links work
- [ ] Renders in < 500ms
---
## Future Enhancements
- **Diff view** - Compare two components side by side
- **History** - See how component changed over time (if git integrated)
- **Documentation** - Allow adding/viewing component descriptions
- **Complexity score** - Calculate a complexity metric
- **Warnings** - Flag potential issues (unused inputs, orphan nodes)
---
## Risks & Mitigations
| Risk | Mitigation |
|------|------------|
| Node type detection misses edge cases | Start with common types, expand based on testing |
| Component inputs/outputs detection fails | Test with various component patterns |
| Too much information overwhelming | Use collapsible sections, start collapsed |
---
## Dependencies
- VIEW-000 Foundation (for traversal and categorization utilities)
## Blocks
- None (independent view)

View File

@@ -0,0 +1,554 @@
# VIEW-003: Trigger Chain Debugger
**View Type:** 🗺️ Meta View (replaces canvas with timeline view)
## Overview
An interactive timeline/sequence view that shows the chain of events from a user action through all the nodes it triggers, across component boundaries. Like a "call stack" for visual programming, with the ability to record live interactions or simulate paths.
**Estimate:** 5-7 days
**Priority:** HIGH
**Complexity:** High
**Dependencies:** VIEW-000 (Foundation), PREREQ-002 (React 19 Debug Fixes)
---
## The Problem
When something goes wrong in a Noodl app:
- You don't know what triggered what
- Events cross component boundaries invisibly
- Race conditions are nearly impossible to debug
- You can't see *when* things happened relative to each other
- Tracing a click through 5 components is manual detective work
The classic question: "The login isn't working. What's happening between the button click and the API call?"
---
## The Solution
A debugger that:
1. **Records** actual runtime events as they happen (live mode)
2. **Traces** potential paths through the graph (static analysis mode)
3. **Shows timing** - when each step happened
4. **Crosses boundaries** - follows data/signals into child components
5. **Highlights failures** - shows where things stopped or errored
---
## User Stories
1. **As a developer debugging**, I want to see exactly what happened when I clicked a button, step by step.
2. **As a developer debugging race conditions**, I want to see the timing of events to understand ordering.
3. **As a developer understanding code**, I want to trace what *would* happen from a given trigger without running the app.
4. **As a developer fixing bugs**, I want to see where in the chain something failed or got blocked.
---
## UI Design
### Main View - Recorded Chain
```
┌─────────────────────────────────────────────────────────────────┐
│ Trigger Chain Debugger [🔴 Record] │
├─────────────────────────────────────────────────────────────────┤
│ Chain: "Login Button Click" Total: 847ms │
│ Started: 14:32:05.123 Steps: 12 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─ Login Page ────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ 1 14:32:05.123 [Button] "Login" clicked │ │ │
│ │ │ └─ onClick signal fired │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ 2ms │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ 2 14:32:05.125 [Function] validateForm │ │ │
│ │ │ ├─ Input: { email: "test@...", password: "***" } │ │ │
│ │ │ └─ Output: Success ✓ │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ 1ms │ │
│ └─────────────────────────┼───────────────────────────────────┘ │
│ │ │
│ ┌─────────────────┴─────────────────┐ │
│ │ ↓ Entering: AuthFlow Component │ │
│ └─────────────────┬─────────────────┘ │
│ │ │
│ ┌─ AuthFlow Component ────┼───────────────────────────────────┐ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ 3 14:32:05.126 [Component Input] loginTriggered │ │ │
│ │ │ └─ Signal received from parent │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ 0ms │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ 4 14:32:05.126 [Variable] isLoading ← true │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌──────────┴──────────┐ │ │
│ │ ▼ ▼ │ │
│ │ ┌────────────────────┐ ┌────────────────────┐ │ │
│ │ │ 5 [States] │ │ 6 [REST] POST │ │ │
│ │ │ → "loading" │ │ /api/auth/login │ │ │
│ │ └────────────────────┘ └────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ ⏳ 523ms │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ 7 14:32:05.649 [REST] Response received │ │ │
│ │ │ ├─ Status: 200 OK │ │ │
│ │ │ └─ Body: { user: { id: 123, name: "..." } } │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ └─────────────────────────┼───────────────────────────────────┘ │
│ │ │
│ ┌─────────────────┴─────────────────┐ │
│ │ ↑ Exiting: AuthFlow Component │ │
│ └─────────────────┬─────────────────┘ │
│ │ │
│ ┌─ Login Page ────────────┼───────────────────────────────────┐ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────────────────┐ │ │
│ │ │ 8 14:32:05.651 [Navigate] → /home │ │ │
│ │ │ └─ ✓ Navigation successful │ │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ [< Prev] Step 8 of 8 [Next >] [Clear] [↗ Jump to Node] │
└─────────────────────────────────────────────────────────────────┘
```
### Error State View
```
┌──────────────────────────────────────────────────────────────┐
│ 7 14:32:05.649 [REST] Response received │
│ ├─ Status: 401 Unauthorized │
│ └─ Body: { error: "Invalid credentials" } │
│ │
│ ❌ ERROR: Request failed │
│ Chain terminated here │
│ │
│ Expected next: [Variable] currentUser │
│ But: Error output triggered instead │
└──────────────────────────────────────────────────────────────┘
```
### Race Condition View
```
┌─────────────────────────────────────────────────────────────────┐
│ ⚠️ POTENTIAL RACE CONDITION DETECTED │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Timeline: │
│ │
│ Chain A (Login) Chain B (Auto-refresh) │
│ ────────────── ─────────────────────── │
│ │
│ 05.123 Button click │
│ 05.125 │ 05.130 Timer fired │
│ 05.126 │ REST call 05.131 │ REST call │
│ │ started │ started │
│ ▼ ▼ │
│ 05.649 │ Response ────────05.645 │ Response │
│ │ │ │
│ 05.651 │ Set user ←───────05.647 │ Set user ← CONFLICT! │
│ ▼ ▼ │
│ │
│ Both chains write to 'currentUser' within 4ms │
│ Final value depends on timing │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Controls
- **[🔴 Record]** - Start/stop recording live events
- **[< Prev] [Next >]** - Step through the chain
- **[Clear]** - Clear recorded chain
- **[↗ Jump to Node]** - Jump to the selected step's node in canvas
- **Click any step** - Select it, show details
- **Hover step** - Preview the node
---
## Technical Design
### Recording Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ RUNTIME │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Node A │───▶│ Node B │───▶│ Node C │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ │ │ │ │
│ │ event │ event │ event │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ TriggerChainRecorder (singleton) ││
│ │ ││
│ │ ┌─────────────────────────────────────────────────────┐ ││
│ │ │ events: [ │ ││
│ │ │ { timestamp, nodeId, type, data, component }, │ ││
│ │ │ { timestamp, nodeId, type, data, component }, │ ││
│ │ │ ... │ ││
│ │ │ ] │ ││
│ │ └─────────────────────────────────────────────────────┘ ││
│ └─────────────────────────────────────────────────────────────┘│
│ │ │
└──────────────────────────────┼───────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ EDITOR │
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ TriggerChainDebuggerView ││
│ │ ││
│ │ - Subscribes to recorder ││
│ │ - Builds visual chain from events ││
│ │ - Groups by component ││
│ │ - Calculates timing ││
│ └─────────────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Data Model
```typescript
interface TriggerEvent {
id: string;
timestamp: number; // High-resolution timestamp
// What happened
nodeId: string;
nodeType: string;
nodeLabel: string;
eventType: 'signal' | 'value-change' | 'api-call' | 'api-response' | 'navigation' | 'error';
// Context
componentName: string;
componentPath: string[]; // Full path for nested components
// Data
inputData?: Record<string, unknown>;
outputData?: Record<string, unknown>;
error?: { message: string; stack?: string };
// Chain info
triggeredBy?: string; // ID of event that caused this one
triggers?: string[]; // IDs of events this one caused
}
interface TriggerChain {
id: string;
name: string; // User-assigned or auto-generated
startTime: number;
endTime: number;
duration: number;
events: TriggerEvent[];
// Organized view
byComponent: Map<string, TriggerEvent[]>;
tree: TriggerEventNode; // Tree structure for rendering
// Analysis
warnings: ChainWarning[];
errors: ChainError[];
}
interface TriggerEventNode {
event: TriggerEvent;
children: TriggerEventNode[];
componentBoundary?: 'enter' | 'exit';
}
```
### Static Analysis Mode
For tracing without running:
```typescript
interface StaticTraceResult {
possiblePaths: TracePath[];
warnings: string[];
}
interface TracePath {
steps: TraceStep[];
probability: 'certain' | 'conditional' | 'unlikely';
conditions: string[]; // What conditions must be true for this path
}
interface TraceStep {
node: NodeGraphNode;
component: ComponentModel;
inputPort?: string;
outputPort?: string;
crossesComponent?: {
from: string;
to: string;
direction: 'into' | 'outof';
};
}
/**
* Trace all possible paths from a starting node.
* Uses graph traversal + condition analysis.
*/
function traceStaticPaths(
project: ProjectModel,
startComponent: ComponentModel,
startNodeId: string,
startPort: string
): StaticTraceResult;
```
---
## Implementation Phases
### Phase 1: Recording Infrastructure (2 days)
1. Create `TriggerChainRecorder` singleton
2. Hook into runtime event system
3. Capture node activations with timestamps
4. Track causal relationships (what triggered what)
5. Handle component boundary crossings
6. Store events in memory (with size limits)
**Verification:**
- [ ] Recorder captures node events
- [ ] Timestamps are accurate
- [ ] Causal chains are tracked
- [ ] Component boundaries marked
### Phase 2: Chain Builder (1 day)
1. Implement chain construction from raw events
2. Group events by component
3. Build tree structure for rendering
4. Calculate timing between steps
5. Detect parallel branches
**Verification:**
- [ ] Chain builds correctly from events
- [ ] Component grouping works
- [ ] Timing calculations accurate
### Phase 3: Basic UI (1.5 days)
1. Create `TriggerChainDebuggerView` component
2. Render event timeline
3. Show component boundaries
4. Display event details
5. Implement step navigation
**Verification:**
- [ ] Timeline renders correctly
- [ ] Component sections visible
- [ ] Step details shown
- [ ] Navigation works
### Phase 4: Recording Controls (0.5 days)
1. Add Record/Stop button
2. Add Clear button
3. Show recording indicator
4. Handle multiple chains
5. Auto-name chains
**Verification:**
- [ ] Can start/stop recording
- [ ] Multiple chains supported
- [ ] UI updates in real-time during recording
### Phase 5: Error & Race Detection (1 day)
1. Detect and highlight errors in chain
2. Implement race condition detection
3. Show warnings for potential issues
4. Add conflict highlighting
**Verification:**
- [ ] Errors clearly shown
- [ ] Race conditions detected
- [ ] Warnings displayed
### Phase 6: Static Analysis Mode (1 day)
1. Implement `traceStaticPaths()`
2. Add "Trace from here" context menu
3. Show possible paths without running
4. Indicate conditional branches
**Verification:**
- [ ] Static trace works
- [ ] Conditional paths shown
- [ ] Context menu integration works
---
## Files to Create
```
packages/noodl-runtime/src/
└── debug/
└── TriggerChainRecorder.ts
packages/noodl-editor/src/editor/src/views/AnalysisPanel/
└── TriggerChainDebuggerView/
├── index.ts
├── TriggerChainDebuggerView.tsx
├── TriggerChainDebuggerView.module.scss
├── ChainTimeline.tsx
├── EventStep.tsx
├── ComponentBoundary.tsx
├── RecordingControls.tsx
├── ChainWarnings.tsx
├── useTriggerChainRecorder.ts
└── staticAnalysis.ts
```
---
## Success Criteria
- [ ] Can record a live interaction chain
- [ ] Shows accurate timing between events
- [ ] Component boundaries clearly marked
- [ ] Can step through chain
- [ ] Can jump to any node in canvas
- [ ] Errors highlighted clearly
- [ ] Race conditions detected
- [ ] Static analysis provides useful paths
- [ ] Performance acceptable (< 5% overhead when recording)
---
## Future Enhancements
- **Export chain** - Export as JSON for bug reports
- **Replay** - Actually replay a recorded chain
- **Breakpoints** - Pause execution at certain nodes
- **Conditional breakpoints** - Break when condition is met
- **Time travel** - Step backwards through chain
- **Chain comparison** - Compare two chains side by side
---
## Risks & Mitigations
| Risk | Mitigation |
|------|------------|
| Performance overhead from recording | Make recording opt-in, use sampling |
| Too many events to display | Filtering, collapsing, pagination |
| Causal tracking misses relationships | Test extensively, allow manual linking |
| Runtime integration complex | Start with key node types, expand |
---
## Dependencies
- VIEW-000 Foundation
- Runtime integration (may require runtime changes)
## Blocks
- None (independent view)
---
## Runtime Integration (CRITICAL)
**This view REQUIRES tight integration with the app preview runtime.** It builds on the same infrastructure that makes nodes "light up" when you interact with the preview.
### Existing Debug Infrastructure
Noodl already has powerful runtime debugging:
- **DebugInspector** - Shows live values when hovering over connections
- **Node highlighting** - Nodes flash/highlight when they fire
- **Data flow visualization** - Connections animate when data flows
The Trigger Chain Debugger extends this by:
1. **Recording** these events instead of just displaying them momentarily
2. **Correlating** events across time to build causal chains
3. **Crossing component boundaries** to show the full picture
4. **Persisting** the chain for step-by-step review
### Integration Points
```typescript
// Existing infrastructure to leverage:
// 1. DebugInspector.InspectorsModel - already tracks pinned values
import { DebugInspector } from '@noodl-models/DebugInspector';
// 2. Node execution events - the runtime emits these
nodeInstance.on('outputChanged', (port, value) => { ... });
nodeInstance.on('signalSent', (port) => { ... });
// 3. Connection highlighting - already exists in NodeGraphEditor
this.highlightConnection(connection, duration);
this.highlightNode(node, duration);
```
### What We Need from Runtime
The recorder needs to subscribe to events that the runtime already emits (or can easily emit):
| Event | Currently Available? | Notes |
|-------|---------------------|-------|
| Node output changed | ✅ Yes | Used for debug inspector |
| Signal fired | ✅ Yes | Used for node highlighting |
| Component entered | ⚠️ Partial | May need enhancement |
| Component exited | ⚠️ Partial | May need enhancement |
| REST call started | ✅ Yes | Visible in network tab |
| REST call completed | ✅ Yes | Visible in network tab |
| Error occurred | ✅ Yes | Shown in console |
### Key Difference from Canvas Highlighting
The existing canvas highlighting shows you what's happening **right now** in **one component at a time**. You have to manually navigate between components to see different parts of the flow.
The Trigger Chain Debugger shows you what happened **over time** across **all components simultaneously**. It's the difference between:
- 🎥 **Existing**: Live TV with one camera angle, you switch channels manually
- 🎬 **New**: Recorded footage with all camera angles synced, scrub through timeline
### Synchronization with Preview
When recording:
1. User clicks [Record] in the debugger view
2. Debugger subscribes to runtime events via existing debug infrastructure
3. User interacts with preview window
4. Events stream into the debugger and build the chain
5. User clicks [Stop]
6. Chain is available for step-through review
When reviewing:
1. User steps through recorded chain
2. Each step highlights the corresponding node on canvas (using existing highlighting)
3. If user clicks "Jump to Node", canvas navigates and selects that node
4. Optionally: replay the highlighting animation at each step
### Implementation Priority
**Phase 1 of this task should focus on discovering and documenting the existing debug event infrastructure** before building UI. Key questions:
- What events does the runtime already emit?
- How does DebugInspector subscribe to them?
- How does node highlighting currently work?
- What's missing for cross-component tracking?

View File

@@ -0,0 +1,444 @@
# VIEW-004: Node Census
**View Type:** 📋 Sidebar Panel (opens alongside canvas)
## Overview
A searchable inventory of every node in a component (or the entire project), grouped by type with automatic duplicate detection and conflict warnings. The "find anything" tool for complex canvases.
**Estimate:** 2-3 days
**Priority:** HIGH
**Complexity:** Low
**Dependencies:** VIEW-000 (Foundation)
---
## The Problem
In a complex canvas:
- You can't find that Variable node you know exists somewhere
- You accidentally create duplicate nodes with the same name
- Two Variables with the same name cause subtle bugs
- You don't know how many REST calls or Functions the component has
- Cleaning up unused nodes requires manual hunting
---
## The Solution
A comprehensive node inventory that:
1. Lists all nodes grouped by type/category
2. Detects duplicates (same name + same type)
3. Warns about potential conflicts
4. Enables quick search by name or type
5. Click to jump to any node
---
## User Stories
1. **As a developer searching**, I want to find a node by name without scrolling around the canvas.
2. **As a developer cleaning up**, I want to see all nodes grouped by type so I can identify unused or redundant ones.
3. **As a developer debugging**, I want to know if I have duplicate Variables that might be causing conflicts.
4. **As a developer auditing**, I want to see a count of each node type to understand component complexity.
---
## UI Design
### Main View
```
┌─────────────────────────────────────────────────────────────────┐
│ Node Census [Scope: Component ▼]│
├─────────────────────────────────────────────────────────────────┤
│ 🔍 Search nodes... │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ⚠️ POTENTIAL ISSUES (3) [Collapse] │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ⚠️ "currentUser" Object appears 2 times │ │
│ │ ├─ Presales Page (near top) [→ Jump] │ │
│ │ └─ Presales Page (near bottom) [→ Jump] │ │
│ │ May cause: Value conflicts, unexpected overwrites │ │
│ │ │ │
│ │ ⚠️ "activeConversation" Variable appears 3 times │ │
│ │ ├─ Presales Page [→ Jump] │ │
│ │ ├─ Presales Page (duplicate!) [→ Jump] │ │
│ │ └─ Chat Component [→ Jump] │ │
│ │ May cause: Race conditions, stale data │ │
│ │ │ │
│ │ ⚠️ "response.output.trigger_" Expression appears 4 times │ │
│ │ Consider: Consolidate or rename for clarity │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 📊 BY CATEGORY │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ▼ 💾 Data (18 nodes) │ │
│ │ ├─ Variable (8) │ │
│ │ │ ├─ activeConversation ×2 ⚠️ [→ Jump] │ │
│ │ │ ├─ presales [→ Jump] │ │
│ │ │ ├─ messageText [→ Jump] │ │
│ │ │ ├─ errorMessage [→ Jump] │ │
│ │ │ └─ ... 3 more │ │
│ │ ├─ Object (6) │ │
│ │ │ ├─ currentUser ×2 ⚠️ [→ Jump] │ │
│ │ │ ├─ userData [→ Jump] │ │
│ │ │ └─ ... 3 more │ │
│ │ └─ Array (4) │ │
│ │ ├─ firstResponderMessages [→ Jump] │ │
│ │ ├─ currentConversationMes... [→ Jump] │ │
│ │ └─ ... 2 more │ │
│ │ │ │
│ │ ▶ ⚡ Logic (22 nodes) │ │
│ │ ▶ 📦 Visual (45 nodes) │ │
│ │ ▶ 🌐 API (8 nodes) │ │
│ │ ▶ 📡 Events (6 nodes) │ │
│ │ ▶ 🔧 Other (9 nodes) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ────────────────────────────────────────────────────────────── │
│ Total: 108 nodes | 3 potential issues │
└─────────────────────────────────────────────────────────────────┘
```
### Search Results View
```
┌─────────────────────────────────────────────────────────────────┐
│ 🔍 currentUser [Clear] │
├─────────────────────────────────────────────────────────────────┤
│ Found 4 matches for "currentUser" │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 💾 "currentUser" Object │ │
│ │ Component: Presales Page │ │
│ │ Connections: 5 outputs connected │ │
│ │ [→ Jump to Node] │ │
│ │ │ │
│ │ 💾 "currentUser" Object │ │
│ │ Component: AuthFlow │ │
│ │ Connections: 3 outputs connected │ │
│ │ [→ Jump to Node] │ │
│ │ │ │
│ │ ⚡ Expression containing "currentUser" │ │
│ │ Component: Presales Page │ │
│ │ Expression: currentUser.name + " - " + ... │ │
│ │ [→ Jump to Node] │ │
│ │ │ │
│ │ 🔧 Function referencing "currentUser" │ │
│ │ Component: App Shell │ │
│ │ [→ Jump to Node] │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### Scope Selector
```
┌────────────────────────────┐
│ Scope: │
│ ○ Current Component │ ← Show nodes in current component only
│ ○ Component + Children │ ← Include subcomponents
│ ● Entire Project │ ← Search all components
└────────────────────────────┘
```
### Interactions
- **Search** - Filter by name, type, or content
- **Click category** - Expand/collapse
- **Click node** - Select (highlight in list)
- **[→ Jump]** - Navigate to node in canvas
- **Scope dropdown** - Change search scope
- **Click issue** - Expand to show all instances
---
## Technical Design
### Data Model
```typescript
interface NodeCensus {
scope: 'component' | 'componentWithChildren' | 'project';
component?: ComponentModel; // If scoped to component
totalNodes: number;
byCategory: {
category: NodeCategory;
count: number;
types: {
typeName: string;
displayName: string;
count: number;
nodes: CensusNode[];
}[];
}[];
duplicates: DuplicateGroup[];
warnings: CensusWarning[];
}
interface CensusNode {
id: string;
type: string;
label: string; // User-assigned label or generated name
displayName: string;
componentName: string;
componentPath: string;
// Connection info
inputCount: number;
outputCount: number;
connectedInputs: number;
connectedOutputs: number;
// For search
searchableContent: string; // Includes parameters, expressions, etc.
// Position hint for "near top/bottom" descriptions
positionHint: 'top' | 'middle' | 'bottom';
}
interface DuplicateGroup {
name: string;
type: string;
instances: CensusNode[];
severity: 'info' | 'warning' | 'error';
reason: string;
suggestion: string;
}
interface CensusWarning {
type: 'duplicate' | 'orphan' | 'complexity' | 'naming';
message: string;
nodes: CensusNode[];
severity: 'info' | 'warning' | 'error';
}
```
### Building the Census
```typescript
function buildNodeCensus(
project: ProjectModel,
scope: CensusScope,
currentComponent?: ComponentModel
): NodeCensus {
const nodes: CensusNode[] = [];
// Collect nodes based on scope
if (scope === 'component' && currentComponent) {
collectNodesFromComponent(currentComponent, nodes);
} else if (scope === 'componentWithChildren' && currentComponent) {
collectNodesRecursive(currentComponent, nodes);
} else {
project.forEachComponent(comp => collectNodesFromComponent(comp, nodes));
}
// Categorize
const byCategory = categorizeNodes(nodes);
// Detect duplicates
const duplicates = findDuplicates(nodes);
// Generate warnings
const warnings = generateWarnings(nodes, duplicates);
return {
scope,
component: currentComponent,
totalNodes: nodes.length,
byCategory,
duplicates,
warnings
};
}
function collectNodesFromComponent(
component: ComponentModel,
nodes: CensusNode[]
): void {
component.graph.forEachNode(node => {
nodes.push({
id: node.id,
type: node.type.name,
label: node.label || getDefaultLabel(node),
displayName: node.type.displayName || node.type.name,
componentName: component.name,
componentPath: component.fullName,
inputCount: node.getPorts('input').length,
outputCount: node.getPorts('output').length,
connectedInputs: countConnectedInputs(component, node),
connectedOutputs: countConnectedOutputs(component, node),
searchableContent: buildSearchableContent(node),
positionHint: calculatePositionHint(node)
});
});
}
```
### Search Implementation
```typescript
function searchNodes(
census: NodeCensus,
query: string
): CensusNode[] {
const lowerQuery = query.toLowerCase();
return census.byCategory
.flatMap(cat => cat.types.flatMap(t => t.nodes))
.filter(node =>
node.label.toLowerCase().includes(lowerQuery) ||
node.type.toLowerCase().includes(lowerQuery) ||
node.displayName.toLowerCase().includes(lowerQuery) ||
node.searchableContent.toLowerCase().includes(lowerQuery)
)
.sort((a, b) => {
// Exact matches first
const aExact = a.label.toLowerCase() === lowerQuery;
const bExact = b.label.toLowerCase() === lowerQuery;
if (aExact && !bExact) return -1;
if (bExact && !aExact) return 1;
// Then by relevance (starts with)
const aStarts = a.label.toLowerCase().startsWith(lowerQuery);
const bStarts = b.label.toLowerCase().startsWith(lowerQuery);
if (aStarts && !bStarts) return -1;
if (bStarts && !aStarts) return 1;
return a.label.localeCompare(b.label);
});
}
```
---
## Implementation Phases
### Phase 1: Data Collection (0.5-1 day)
1. Implement `buildNodeCensus()` function
2. Implement scope handling (component, recursive, project)
3. Build searchable content from node parameters
4. Calculate position hints
**Verification:**
- [ ] All nodes collected correctly
- [ ] Scopes work as expected
- [ ] Searchable content includes expressions, URLs, etc.
### Phase 2: Categorization & Duplicates (0.5 day)
1. Implement node categorization
2. Implement duplicate detection
3. Generate warnings with appropriate severity
4. Create suggestions for fixing issues
**Verification:**
- [ ] Categories correct for all node types
- [ ] Duplicates detected reliably
- [ ] Warnings helpful
### Phase 3: Basic UI (1 day)
1. Create `NodeCensusView` component
2. Implement collapsible category tree
3. Show duplicate warnings section
4. Add scope selector
5. Display node counts
**Verification:**
- [ ] Tree renders correctly
- [ ] Collapse/expand works
- [ ] Warnings display prominently
### Phase 4: Search & Navigation (0.5-1 day)
1. Implement search input with filtering
2. Add keyboard navigation
3. Implement "Jump to Node" navigation
4. Add search result highlighting
**Verification:**
- [ ] Search filters correctly
- [ ] Results update live
- [ ] Jump to node works across components
### Phase 5: Polish (0.5 day)
1. Add loading states
2. Improve typography and icons
3. Add empty states
4. Performance optimization for large projects
**Verification:**
- [ ] UI polished
- [ ] Large projects handled well
- [ ] Responsive
---
## Files to Create
```
packages/noodl-editor/src/editor/src/views/AnalysisPanel/
└── NodeCensusView/
├── index.ts
├── NodeCensusView.tsx
├── NodeCensusView.module.scss
├── CategoryTree.tsx
├── NodeListItem.tsx
├── DuplicateWarnings.tsx
├── SearchInput.tsx
├── ScopeSelector.tsx
└── useNodeCensus.ts
```
---
## Success Criteria
- [ ] All nodes in scope appear in census
- [ ] Categories correctly assigned
- [ ] Duplicates detected and warned
- [ ] Search finds nodes by name, type, and content
- [ ] Jump to node works reliably
- [ ] Scope switching works
- [ ] Renders fast (< 500ms) for 200+ nodes
---
## Future Enhancements
- **Bulk actions** - Select multiple nodes, delete orphans
- **Export** - Export node list as CSV
- **Comparison** - Compare census between two components
- **History** - Track node count over time
- **Orphan detection** - Find nodes with no connections
---
## Risks & Mitigations
| Risk | Mitigation |
|------|------------|
| Large projects slow to census | Cache results, incremental updates |
| False positive duplicates | Allow user to dismiss warnings |
| Categorization misses node types | Maintain mapping, default to "Other" |
---
## Dependencies
- VIEW-000 Foundation (for categorization utilities)
## Blocks
- None (independent view)

View File

@@ -0,0 +1,527 @@
# VIEW-005: Data Lineage View
**View Type:** 🎨 Canvas Overlay (enhances existing canvas with highlighting)
## Overview
A complete trace of where any value originates and where it flows to, crossing component boundaries. The "where does this come from?" and "where does this go?" tool. **Highlights persist on the canvas until dismissed!**
**Estimate:** 3-4 days
**Priority:** HIGH
**Complexity:** Medium
**Dependencies:** VIEW-000 (Foundation), PREREQ-004 (Canvas Highlighting API)
---
## The Problem
In a complex Noodl project:
- Data comes from parent components, API calls, user input... but where exactly?
- A value passes through 5 transformations before reaching its destination
- Component boundaries hide the full picture
- "The login data isn't showing up" - but why? Where did it get lost?
The question: "I'm looking at this `userName` value in a Text node. Where does it actually come from? The user input? The database? A parent component?"
---
## The Solution
A visual lineage trace that:
1. Shows the complete upstream path (all the way to the source)
2. Shows the complete downstream path (all the way to final usage)
3. Crosses component boundaries transparently
4. Shows each transformation along the way
5. Allows jumping to any point in the chain
---
## User Stories
1. **As a developer debugging**, I want to trace where a value originates to understand why it's wrong.
2. **As a developer understanding code**, I want to see what a value feeds into to understand its impact.
3. **As a developer refactoring**, I want to know the full data flow before changing a node.
4. **As a new team member**, I want to understand how data moves through the application.
---
## UI Design
### Lineage View
```
┌─────────────────────────────────────────────────────────────────┐
│ Data Lineage [↗ Canvas] [Refresh] │
├─────────────────────────────────────────────────────────────────┤
│ Selected: messageText (in Create chat message) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ▲ UPSTREAM (where does this value come from?) │
│ ═══════════════════════════════════════════════════════════════ │
│ │
│ ┌─ App Shell ─────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 🌐 REST: GET /api/user │ │ │
│ │ │ Response.body.name │ │ │
│ │ └────────────────────┬────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ .name │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 💾 currentUser (Object) │ │ │
│ │ │ Stores user data from API │ │ │
│ │ └────────────────────┬────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ → Component Output │ │
│ │ │ │
│ └────────────────────────┼────────────────────────────────────┘ │
│ │ │
│ ╔═══════════════╧═══════════════╗ │
│ ║ Crosses into: Presales Page ║ │
│ ╚═══════════════╤═══════════════╝ │
│ │ │
│ ┌─ Presales Page ────────┼────────────────────────────────────┐ │
│ │ ▼ ← Component Input │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 💾 userData (Object) │ │ │
│ │ │ Local reference to user │ │ │
│ │ └────────────────────┬────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ .name property │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ ⚡ String Format │ │ │
│ │ │ "{name}: {message}" │ │ │
│ │ └────────────────────┬────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ Result │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ ★ messageText (Selected) │ │ │
│ │ │ The value you asked about │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ═══════════════════════════════════════════════════════════════ │
│ ▼ DOWNSTREAM (where does this value go?) │
│ │
│ ┌─ Presales Page ─────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ ★ messageText │ │ │
│ │ └────────────────────┬────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌─────────────┴─────────────┐ │ │
│ │ ▼ ▼ │ │
│ │ ┌─────────────────┐ ┌─────────────────────────────┐ │ │
│ │ │ 🧩 Create chat │ │ 💾 firstResponderMessages │ │ │
│ │ │ message │ │ (Array) │ │ │
│ │ │ .messageText │ │ Stores for history │ │ │
│ │ └────────┬────────┘ └─────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ 🌐 REST: POST /api/messages │ │ │
│ │ │ Sends to server │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ Path summary: REST response → Object → Component I/O → │
│ Object → String Format → [HERE] → REST + Array │
│ │
│ [Click any node to jump to canvas] │
└─────────────────────────────────────────────────────────────────┘
```
### Compact Lineage (for sidebar)
```
┌───────────────────────────────────────┐
│ messageText Lineage │
├───────────────────────────────────────┤
│ ▲ FROM: │
│ REST /api/user │
│ ↓ .name │
│ currentUser (Object) │
│ ↓ Component boundary │
│ userData (Object) │
│ ↓ .name │
│ String Format │
│ ↓ │
│ ★ messageText │
│ │
│ ▼ TO: │
│ ├─ Create chat message │
│ │ ↓ │
│ │ REST POST /api/messages │
│ └─ firstResponderMessages │
└───────────────────────────────────────┘
```
### Interactions
- **Select any node on canvas** - Shows lineage for that node
- **Click node in lineage** - Jump to that node on canvas
- **Hover node** - Highlight on canvas
- **[Expand/Collapse]** - Toggle upstream/downstream sections
- **[Refresh]** - Recalculate lineage
- **Click path summary** - Highlight full path on canvas
---
## Technical Design
### Data Model
```typescript
interface LineageResult {
selectedNode: {
id: string;
label: string;
type: string;
componentName: string;
};
upstream: LineagePath;
downstream: LineagePath[]; // Can branch to multiple destinations
}
interface LineagePath {
steps: LineageStep[];
crossings: ComponentCrossing[];
}
interface LineageStep {
node: NodeGraphNode;
component: ComponentModel;
port: string;
portType: 'input' | 'output';
transformation?: string; // Description of what happens (.name, Expression, etc.)
isSource?: boolean; // True if this is the ultimate origin
isSink?: boolean; // True if this is a final destination
}
interface ComponentCrossing {
from: ComponentModel;
to: ComponentModel;
viaPort: string;
direction: 'into' | 'outof';
stepIndex: number; // Where in the path this crossing occurs
}
```
### Building Lineage
```typescript
function buildLineage(
project: ProjectModel,
component: ComponentModel,
nodeId: string,
port?: string // Optional: specific port to trace
): LineageResult {
const node = component.graph.findNodeWithId(nodeId);
// Trace upstream (find sources)
const upstream = traceUpstream(project, component, node, port);
// Trace downstream (find destinations)
const downstream = traceDownstream(project, component, node, port);
return {
selectedNode: {
id: node.id,
label: node.label || getDefaultLabel(node),
type: node.type.name,
componentName: component.name
},
upstream,
downstream
};
}
function traceUpstream(
project: ProjectModel,
component: ComponentModel,
node: NodeGraphNode,
port?: string,
visited: Set<string> = new Set()
): LineagePath {
const steps: LineageStep[] = [];
const crossings: ComponentCrossing[] = [];
// Prevent infinite loops
const nodeKey = `${component.fullName}:${node.id}`;
if (visited.has(nodeKey)) {
return { steps, crossings };
}
visited.add(nodeKey);
// Get input connections
const inputs = port
? getConnectionsToPort(component, node.id, port)
: getAllInputConnections(component, node.id);
for (const connection of inputs) {
const sourceNode = component.graph.findNodeWithId(connection.fromId);
steps.push({
node: sourceNode,
component,
port: connection.fromProperty,
portType: 'output',
transformation: describeTransformation(sourceNode, connection.fromProperty)
});
// Check if this is a Component Input (crosses boundary)
if (sourceNode.type.name === 'Component Inputs') {
const parentInfo = findParentConnection(project, component, connection.fromProperty);
if (parentInfo) {
crossings.push({
from: parentInfo.parentComponent,
to: component,
viaPort: connection.fromProperty,
direction: 'into',
stepIndex: steps.length
});
// Continue tracing in parent component
const parentLineage = traceUpstream(
project,
parentInfo.parentComponent,
parentInfo.sourceNode,
parentInfo.sourcePort,
visited
);
steps.push(...parentLineage.steps);
crossings.push(...parentLineage.crossings);
}
} else if (!isSourceNode(sourceNode)) {
// Continue tracing recursively
const prevLineage = traceUpstream(project, component, sourceNode, undefined, visited);
steps.push(...prevLineage.steps);
crossings.push(...prevLineage.crossings);
} else {
// Mark as source
steps[steps.length - 1].isSource = true;
}
}
return { steps, crossings };
}
function isSourceNode(node: NodeGraphNode): boolean {
// These node types are considered "sources" - don't trace further
const sourceTypes = [
'REST', // API response is a source
'Variable', // Unless we want to trace where it was set
'Object',
'Page Inputs',
'Receive Event',
'Function', // Function output is a source
'String', // Literal values
'Number',
'Boolean'
];
return sourceTypes.includes(node.type.name);
}
```
---
## Implementation Phases
### Phase 1: Basic Upstream Tracing (1 day)
1. Implement `traceUpstream()` within a single component
2. Follow connections to find sources
3. Handle branching (multiple inputs)
4. Detect source nodes (REST, Variable, etc.)
**Verification:**
- [ ] Can trace simple linear chains
- [ ] Handles multiple inputs
- [ ] Stops at source nodes
### Phase 2: Cross-Component Tracing (1 day)
1. Handle Component Inputs nodes
2. Find parent component context
3. Continue trace in parent
4. Handle Component Outputs similarly
5. Track component crossings
**Verification:**
- [ ] Crosses into parent components
- [ ] Crosses into child components
- [ ] Crossings tracked correctly
### Phase 3: Downstream Tracing (0.5-1 day)
1. Implement `traceDownstream()` with similar logic
2. Handle branching (one output to multiple destinations)
3. Track all destination paths
**Verification:**
- [ ] Finds all destinations
- [ ] Handles branching
- [ ] Crosses component boundaries
### Phase 4: UI Implementation (1 day)
1. Create `DataLineageView` component
2. Render upstream path with component sections
3. Render downstream paths (tree structure)
4. Show component boundary crossings
5. Add path summary
**Verification:**
- [ ] Lineage renders correctly
- [ ] Component sections clear
- [ ] Crossings visually distinct
### Phase 5: Interactivity & Polish (0.5 day)
1. Click to navigate to node
2. Hover to highlight on canvas
3. Context menu integration ("Show Lineage")
4. Handle edge cases (orphan nodes, cycles)
**Verification:**
- [ ] Navigation works
- [ ] Context menu works
- [ ] Edge cases handled gracefully
---
## Files to Create
```
packages/noodl-editor/src/editor/src/views/AnalysisPanel/
└── DataLineageView/
├── index.ts
├── DataLineageView.tsx
├── DataLineageView.module.scss
├── LineagePath.tsx
├── LineageStep.tsx
├── ComponentSection.tsx
├── CrossingIndicator.tsx
├── PathSummary.tsx
└── useDataLineage.ts
packages/noodl-editor/src/editor/src/utils/graphAnalysis/
└── lineage.ts
```
---
## Success Criteria
- [ ] Traces upstream to source correctly
- [ ] Traces downstream to all destinations
- [ ] Crosses component boundaries
- [ ] Shows transformations along the way
- [ ] Path summary is accurate
- [ ] Navigation to any step works
- [ ] Handles cycles without infinite loops
- [ ] Renders in < 1s for complex paths
---
## Runtime Integration (OPTIONAL BUT POWERFUL)
While Data Lineage is primarily a **static analysis** tool (showing the graph structure), it becomes dramatically more powerful with **live runtime integration**.
### Static vs Live Mode
| Mode | What it shows | Runtime needed? |
|------|---------------|-----------------|
| **Static** | The *path* data takes through the graph | No |
| **Live** | The *path* + *actual current values* at each step | Yes |
### Live Value Display
Imagine the lineage view showing not just the path, but the actual data at each step:
```
┌─────────────────────────────────────────────────────────────┐
│ ▲ UPSTREAM │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 🌐 REST: GET /api/user │ │
│ │ Response.body.name │ │
│ │ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ │ │
│ │ 📍 LIVE: "John Smith" [last: 2s ago]│ │
│ └────────────────────┬────────────────────────────────┘ │
│ │ │
│ ▼ .name │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 💾 currentUser (Object) │ │
│ │ ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ │ │
│ │ 📍 LIVE: { name: "John Smith", email: "j@..." } │ │
│ └────────────────────┬────────────────────────────────┘ │
│ ▼ │
```
This answers "where does this come from?" AND "what's the actual value right now?" in one view.
### Integration with Existing Debug Infrastructure
The live values can come from the same system that powers:
- **DebugInspector hover values** - Already shows live values on connection hover
- **Pinned inspectors** - Already tracks values over time
```typescript
// Leverage existing infrastructure:
import { DebugInspector } from '@noodl-models/DebugInspector';
function getLiveValueForNode(nodeId: string, port: string): unknown {
// DebugInspector already knows how to get live values
return DebugInspector.instance.getValueForPort(nodeId, port);
}
```
### Implementation Approach
1. **Start with static-only** (Phase 1-5 as documented)
2. **Add live mode toggle** (Phase 6 - optional enhancement)
3. **Subscribe to value changes** for nodes in the lineage path
4. **Update display** when values change
### Syncing with Canvas Highlighting
When the user hovers over a step in the lineage view:
- Highlight that node on the canvas (using existing highlighting)
- If the node is in a different component, show a "navigate" prompt
- Optionally flash the connection path on canvas
## Future Enhancements
- **Path highlighting** - Highlight entire path on canvas
- **Breakpoint insertion** - Add debug breakpoints along the path
- **Path comparison** - Compare two different lineage paths
- **Export** - Export lineage as documentation
---
## Risks & Mitigations
| Risk | Mitigation |
|------|------------|
| Deep component nesting | Limit depth, show "continue" option |
| Cycles in graph | Track visited nodes, break cycles |
| Many branches overwhelm UI | Collapse by default, expand on demand |
| Performance on complex graphs | Cache results, lazy expansion |
---
## Dependencies
- VIEW-000 Foundation (for cross-component resolution)
## Blocks
- None (independent view)

View File

@@ -0,0 +1,463 @@
# VIEW-006: Impact Radar
**View Type:** 🎨 Canvas Overlay (enhances existing canvas with highlighting)
## Overview
Before you change something, see everywhere it's used and what might break. A pre-change impact analysis tool that shows the blast radius of modifications. **Highlights persist on the canvas until dismissed!**
**Estimate:** 3-4 days
**Priority:** MEDIUM
**Complexity:** Medium
**Dependencies:** VIEW-000 (Foundation), PREREQ-004 (Canvas Highlighting API)
---
## The Problem
When you want to change something in a Noodl project:
- You don't know everywhere a component is used
- You don't know what depends on a specific output
- Changing a component's interface might break 5 other places
- Renaming a variable might affect components you forgot about
- "I'll just change this real quick" → breaks production
---
## The Solution
An impact analysis that:
1. Shows everywhere a component/node is used
2. Shows what depends on specific outputs
3. Warns about breaking changes
4. Lets you preview "what if I change this?"
5. Provides a checklist of things to update
---
## User Stories
1. **As a developer refactoring**, I want to know everywhere this component is used before I change its interface.
2. **As a developer renaming**, I want to see all places that reference this name so I don't break anything.
3. **As a developer removing**, I want to know if anything depends on this before I delete it.
4. **As a developer planning**, I want to understand the impact of a proposed change before implementing it.
---
## UI Design
### Component Impact View
```
┌─────────────────────────────────────────────────────────────────┐
│ Impact Radar [Refresh] │
├─────────────────────────────────────────────────────────────────┤
│ Analyzing: AuthFlow Component │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 📊 USAGE SUMMARY │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Used in 3 components across 2 pages │ │
│ │ │ │
│ │ Impact level: 🟡 MEDIUM │ │
│ │ Reason: Changes affect multiple pages │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 📍 USAGE LOCATIONS │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─ Login Page ────────────────────────────────────────────┐ │ │
│ │ │ Instance: authFlow_1 [→ Jump] │ │ │
│ │ │ │ │ │
│ │ │ Connected inputs: │ │ │
│ │ │ • onLoginRequest ← LoginButton.onClick │ │ │
│ │ │ • redirectUrl ← "/dashboard" (static) │ │ │
│ │ │ │ │ │
│ │ │ Connected outputs: │ │ │
│ │ │ • currentUser → NavBar.user, ProfileWidget.user │ │ │
│ │ │ • onSuccess → Navigate("/dashboard") │ │ │
│ │ │ • authError → ErrorToast.message │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─ Settings Page ─────────────────────────────────────────┐ │ │
│ │ │ Instance: authFlow_2 [→ Jump] │ │ │
│ │ │ │ │ │
│ │ │ Connected inputs: │ │ │
│ │ │ • onLogoutRequest ← LogoutButton.onClick │ │ │
│ │ │ │ │ │
│ │ │ Connected outputs: │ │ │
│ │ │ • onSuccess → Navigate("/login") │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─ App Shell ─────────────────────────────────────────────┐ │ │
│ │ │ Instance: authFlow_global [→ Jump] │ │ │
│ │ │ │ │ │
│ │ │ Connected outputs: │ │ │
│ │ │ • isAuthenticated → RouteGuard.condition │ │ │
│ │ │ • currentUser → (passed to 12 child components) │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ⚠️ CHANGE IMPACT ANALYSIS │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ If you MODIFY these outputs: │ │
│ │ │ │
│ │ • currentUser Impact: 🔴 HIGH │ │
│ │ └─ Used by 14 consumers across 3 components │ │
│ │ └─ Breaking changes will affect: NavBar, ProfileWidget, │ │
│ │ UserSettings, ChatHeader, and 10 more... │ │
│ │ │ │
│ │ • onSuccess Impact: 🟡 MEDIUM │ │
│ │ └─ Used by 3 consumers │ │
│ │ └─ Navigation flows depend on this signal │ │
│ │ │ │
│ │ • authError Impact: 🟢 LOW │ │
│ │ └─ Used by 2 consumers │ │
│ │ └─ Only affects error display │ │
│ │ │ │
│ │ If you REMOVE these inputs: │ │
│ │ │ │
│ │ • redirectUrl Impact: 🟡 MEDIUM │ │
│ │ └─ Login Page passes static value │ │
│ │ └─ Would need to handle internally or change caller │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 📋 CHANGE CHECKLIST │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ If changing currentUser output structure: │ │
│ │ □ Update Login Page → NavBar connection │ │
│ │ □ Update Login Page → ProfileWidget connection │ │
│ │ □ Update App Shell → RouteGuard connection │ │
│ │ □ Update App Shell → 12 child components │ │
│ │ □ Test login flow on Login Page │ │
│ │ □ Test auth guard on protected routes │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Port-Specific Impact
```
┌─────────────────────────────────────────────────────────────────┐
│ Impact Radar: currentUser output │
├─────────────────────────────────────────────────────────────────┤
│ │
│ This output feeds into: │
│ │
│ ┌─────────────────────┐ │
│ │ AuthFlow │ │
│ │ currentUser output │ │
│ └──────────┬──────────┘ │
│ │ │
│ ┌─────────────┼─────────────┬─────────────┐ │
│ ▼ ▼ ▼ ▼ │
│ ┌────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ NavBar │ │ Profile │ │ Settings │ │ +9 more │ │
│ │ .user │ │ Widget │ │ .user │ │ │ │
│ └────────┘ │ .user │ └──────────┘ └──────────┘ │
│ └──────────┘ │
│ │
│ Properties accessed: │
│ • .name (used in 8 places) │
│ • .email (used in 3 places) │
│ • .avatar (used in 4 places) │
│ • .role (used in 2 places) │
│ │
│ ⚠️ If you change the structure of currentUser: │
│ All 17 usages will need to be verified │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### Quick Impact Badge (for Components Panel)
```
┌────────────────────────────────────────┐
│ 🧩 AuthFlow (×3) 🔴 │
│ Used in 3 places, HIGH impact │
└────────────────────────────────────────┘
```
---
## Technical Design
### Data Model
```typescript
interface ImpactAnalysis {
target: {
type: 'component' | 'node' | 'port';
component?: ComponentModel;
node?: NodeGraphNode;
port?: string;
};
summary: {
usageCount: number;
impactLevel: 'low' | 'medium' | 'high' | 'critical';
reason: string;
};
usages: ComponentUsageDetail[];
portImpacts: PortImpact[];
changeChecklist: ChecklistItem[];
}
interface ComponentUsageDetail {
component: ComponentModel; // Where it's used
instanceNodeId: string; // The instance node
instanceLabel: string;
connectedInputs: {
port: string;
connections: {
fromNode: NodeGraphNode;
fromPort: string;
isStatic: boolean;
staticValue?: unknown;
}[];
}[];
connectedOutputs: {
port: string;
connections: {
toNode: NodeGraphNode;
toPort: string;
transitiveUsages?: number; // How many things depend on this downstream
}[];
}[];
}
interface PortImpact {
port: string;
direction: 'input' | 'output';
impactLevel: 'low' | 'medium' | 'high' | 'critical';
consumerCount: number;
consumers: {
component: string;
node: string;
port: string;
}[];
propertiesAccessed?: string[]; // For object outputs, what properties are used
}
interface ChecklistItem {
action: string;
component: string;
priority: 'required' | 'recommended' | 'optional';
completed: boolean;
}
```
### Building Impact Analysis
```typescript
function analyzeImpact(
project: ProjectModel,
target: ImpactTarget
): ImpactAnalysis {
if (target.type === 'component') {
return analyzeComponentImpact(project, target.component);
} else if (target.type === 'node') {
return analyzeNodeImpact(project, target.component, target.node);
} else {
return analyzePortImpact(project, target.component, target.node, target.port);
}
}
function analyzeComponentImpact(
project: ProjectModel,
component: ComponentModel
): ImpactAnalysis {
// Find all usages
const usages = findComponentUsages(project, component.fullName);
// Analyze each port
const portImpacts: PortImpact[] = [];
// Outputs - what depends on them?
component.getPorts().filter(p => p.plug === 'output').forEach(port => {
const consumers = findPortConsumers(project, component, port.name, usages);
portImpacts.push({
port: port.name,
direction: 'output',
impactLevel: calculateImpactLevel(consumers.length),
consumerCount: consumers.length,
consumers,
propertiesAccessed: analyzePropertyAccess(consumers)
});
});
// Inputs - what provides them?
component.getPorts().filter(p => p.plug === 'input').forEach(port => {
const providers = findPortProviders(project, component, port.name, usages);
portImpacts.push({
port: port.name,
direction: 'input',
impactLevel: calculateImpactLevel(providers.length),
consumerCount: providers.length,
consumers: providers
});
});
// Calculate overall impact
const maxImpact = Math.max(...portImpacts.map(p => impactScore(p.impactLevel)));
// Generate checklist
const checklist = generateChecklist(component, usages, portImpacts);
return {
target: { type: 'component', component },
summary: {
usageCount: usages.length,
impactLevel: scoreToLevel(maxImpact),
reason: generateImpactReason(usages, portImpacts)
},
usages: usages.map(u => buildUsageDetail(project, component, u)),
portImpacts,
changeChecklist: checklist
};
}
function calculateImpactLevel(consumerCount: number): ImpactLevel {
if (consumerCount === 0) return 'low';
if (consumerCount <= 2) return 'low';
if (consumerCount <= 5) return 'medium';
if (consumerCount <= 10) return 'high';
return 'critical';
}
```
---
## Implementation Phases
### Phase 1: Usage Finding (1 day)
1. Build on VIEW-000's `findComponentUsages()`
2. Add detailed connection analysis per usage
3. Track which ports are connected where
4. Count transitive dependencies
**Verification:**
- [ ] Finds all component usages
- [ ] Connection details accurate
- [ ] Transitive counts correct
### Phase 2: Impact Calculation (0.5-1 day)
1. Calculate impact level per port
2. Analyze property access patterns
3. Generate overall impact summary
4. Create impact reasons
**Verification:**
- [ ] Impact levels sensible
- [ ] Property analysis works
- [ ] Summary helpful
### Phase 3: UI - Usage Display (1 day)
1. Create `ImpactRadarView` component
2. Show usage locations with details
3. Display per-port impact
4. Add navigation to usages
**Verification:**
- [ ] Usages displayed clearly
- [ ] Port impacts visible
- [ ] Navigation works
### Phase 4: Checklist Generation (0.5 day)
1. Generate change checklist from analysis
2. Allow marking items complete
3. Prioritize checklist items
**Verification:**
- [ ] Checklist comprehensive
- [ ] Priorities sensible
- [ ] Can mark complete
### Phase 5: Polish & Integration (0.5-1 day)
1. Add impact badges to Components Panel
2. Context menu "Show Impact"
3. Quick impact preview on hover
4. Performance optimization
**Verification:**
- [ ] Badges show in panel
- [ ] Context menu works
- [ ] Performance acceptable
---
## Files to Create
```
packages/noodl-editor/src/editor/src/views/AnalysisPanel/
└── ImpactRadarView/
├── index.ts
├── ImpactRadarView.tsx
├── ImpactRadarView.module.scss
├── UsageSummary.tsx
├── UsageLocation.tsx
├── PortImpactList.tsx
├── ChangeChecklist.tsx
├── ImpactBadge.tsx
└── useImpactAnalysis.ts
packages/noodl-editor/src/editor/src/utils/graphAnalysis/
└── impact.ts
```
---
## Success Criteria
- [ ] Shows all places component is used
- [ ] Impact level calculation sensible
- [ ] Port-level analysis accurate
- [ ] Checklist helpful for changes
- [ ] Navigation to usages works
- [ ] Renders in < 1s
---
## Future Enhancements
- **"What if" simulation** - Preview changes without making them
- **Diff preview** - Show what would change in each usage
- **Auto-update** - Automatically update simple changes across usages
- **Impact history** - Track changes and their actual impact over time
---
## Risks & Mitigations
| Risk | Mitigation |
|------|------------|
| Too many usages to display | Pagination, grouping, filtering |
| Property analysis misses patterns | Start simple, expand based on real usage |
| Impact levels feel wrong | Make configurable, learn from feedback |
---
## Dependencies
- VIEW-000 Foundation
- VIEW-001 (optional, for visual representation)
## Blocks
- None (independent view)

View File

@@ -0,0 +1,497 @@
# 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.

View File

@@ -0,0 +1,346 @@
# VIEW-PREREQ: Prerequisites & Modernization Roadmap
## Overview
**⚠️ DO NOT START VIEW IMPLEMENTATION until these prerequisites are complete.**
Before implementing the Canvas Visualization Views, several parts of the codebase need attention. This document maps out what needs to be done and in what order.
---
## Critical Path
```
┌─────────────────────────────────────────────────────────────────────┐
│ PREREQ-001: Fix Webpack Caching │
│ ══════════════════════════════════════════════════════════════ │
│ STATUS: BLOCKING EVERYTHING │
│ Without this fix, you cannot test any code changes reliably. │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ PREREQ-002: React 19 Debug Fixes PREREQ-003: Document Overlays │
│ (0.5-1 day) (1-2 days) │
│ Can run in parallel │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ VIEW-000: Foundation │
│ (4-5 days) │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ PREREQ-004: Canvas Highlighting API │
│ (1-2 days) │
│ Needed for canvas overlays │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ VIEW IMPLEMENTATION │
└─────────────────────────────────────────────────────────────────────┘
```
---
## Current State Assessment
### ✅ Already Modernized (Ready to Use)
| Component | Status | Notes |
|-----------|--------|-------|
| **SidebarModel** | React-ready | Already supports React components via `panel` prop |
| **SidePanel.tsx** | React | Container that hosts sidebar panels |
| **NodeGraphContext** | React | Provides `switchToComponent()` - perfect for navigation |
| **CommentLayer** | React overlay on canvas | **Template for how to add canvas overlays!** |
### ⚠️ In Progress / Blocked
| Component | Status | Blocker | Impact on Views |
|-----------|--------|---------|-----------------|
| **ComponentsPanel** | 85% migrated | Webpack 5 caching | Census badges, Topology highlighting |
| **React 19 fixes** | Partial | Legacy ReactDOM.render() calls | Debug infrastructure for Trigger Chain |
### ❌ Legacy (Needs Modernization)
| Component | Lines | Current State | Impact on Views |
|-----------|-------|---------------|-----------------|
| **NodeGraphEditor.ts** | ~2000+ | Monolithic jQuery | Canvas overlays, highlighting |
| **DebugInspector** | ~400 | Legacy React patterns | Trigger Chain, Data Lineage live values |
| **TextStylePicker** | ~200 | Legacy ReactDOM | Minor |
---
## Critical Path Analysis
### For Canvas Overlays (Layers, Lineage Highlighting, Impact Highlighting)
The **CommentLayer** is already a working example of a React overlay on the canvas! It:
- Renders React components over the canvas
- Responds to canvas zoom/pan
- Integrates with selection system
**What we need:**
1. Study CommentLayer pattern
2. Create generic `CanvasOverlay` system based on it
3. Clean hooks in NodeGraphEditor for:
- Node/connection highlighting (partially exists)
- Overlay coordinate transforms
- Event interception for overlay interactions
### For Meta Views (Topology, Trigger Chain)
These **replace** the canvas, so they need less from NodeGraphEditor. But they need:
1. View Switcher in the header
2. Access to ProjectModel (already available)
3. Navigation back to canvas (NodeGraphContext already provides this)
### For Sidebar Panels (X-Ray, Census panels)
SidebarModel already supports React, so these can be built now. But for **integration features** like:
- Highlighting nodes from Census panel
- Showing badges in ComponentsPanel
...we need the blocked work resolved.
---
## Recommended Prerequisite Tasks
### PREREQ-001: Resolve Webpack 5 Caching Issue
**Priority:** CRITICAL
**Estimate:** 1-2 days
**Blocks:** Everything that touches existing code
The ComponentsPanel migration revealed that Webpack 5 persistent caching prevents code changes from loading. This will block ALL development, not just views.
**Options:**
1. Disable persistent caching in dev mode
2. Configure cache invalidation properly
3. Add cache-busting to build process
**Must fix first** - otherwise we can't test any changes reliably.
### PREREQ-002: Complete React 19 Migration for Debug Infrastructure
**Priority:** HIGH
**Estimate:** 0.5-1 day
**Blocks:** VIEW-003 (Trigger Chain), VIEW-005 (Data Lineage live values)
Files that need fixing:
```
nodegrapheditor.debuginspectors.js → Uses legacy ReactDOM.render()
commentlayer.ts → Creates new createRoot() on every render (already noted)
TextStylePicker.jsx → Uses legacy ReactDOM.render()
```
These are causing crashes in the debug inspector system which we need for Trigger Chain.
### PREREQ-003: Document/Stabilize Canvas Overlay Pattern
**Priority:** HIGH
**Estimate:** 1-2 days
**Blocks:** VIEW-007 (Semantic Layers), Lineage highlighting, Impact highlighting
CommentLayer already works as an overlay. We need to:
1. Document how it works
2. Extract reusable patterns
3. Create `CanvasOverlayManager` that can host multiple overlays
4. Define the coordinate transform API
This doesn't require NodeGraphEditor refactoring - just understanding and formalizing what exists.
### PREREQ-004: Add Canvas Highlighting API
**Priority:** MEDIUM
**Estimate:** 1-2 days
**Blocks:** Persistent lineage highlighting, impact highlighting on canvas
The canvas already highlights nodes momentarily (for debug). We need:
1. Persistent highlighting (stays until dismissed)
2. Connection path highlighting
3. Multiple highlight "channels" (lineage = blue, impact = orange, etc.)
4. API for external code to control highlights
```typescript
// Desired API:
interface CanvasHighlightAPI {
highlightNodes(nodeIds: string[], options: HighlightOptions): HighlightHandle;
highlightConnections(connections: Connection[], options: HighlightOptions): HighlightHandle;
highlightPath(path: PathDefinition, options: HighlightOptions): HighlightHandle;
}
interface HighlightHandle {
update(nodeIds: string[]): void; // Change what's highlighted
dismiss(): void; // Remove highlighting
}
interface HighlightOptions {
color?: string;
style?: 'solid' | 'pulse' | 'glow';
persistent?: boolean; // Stay until explicitly dismissed
channel?: string; // 'lineage', 'impact', 'selection', etc.
}
```
### PREREQ-005: Complete ComponentsPanel React Migration
**Priority:** MEDIUM
**Estimate:** 2-3 days (after webpack fix)
**Blocks:** Census badges in panel, Topology component highlighting
The migration is 85% done. Once webpack caching is fixed:
1. Verify the existing React code works
2. Complete remaining features
3. Add extension points for badges/highlighting
### PREREQ-006: NodeGraphEditor Partial Modernization (Optional)
**Priority:** LOW (for views project)
**Estimate:** 5-10 days
**Nice to have, not blocking**
The full canvas modernization project is documented in `CANVAS-MODERNISATION-PROJECT.md`. For the views project, we DON'T need the full refactor. We just need clean interfaces for:
- Overlay rendering
- Highlighting
- Navigation
These can be added incrementally without the full breakup.
---
## Recommended Order
```
┌─────────────────────────────────────────────────────────────────────┐
│ PHASE 0: Unblock │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ PREREQ-001: Fix Webpack Caching │ │
│ │ (CRITICAL - everything else depends on this) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ PHASE 1: Foundation (Parallel) │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ PREREQ-002: │ │ PREREQ-003: │ │
│ │ React 19 Debug Fixes │ │ Document Overlay │ │
│ │ (0.5-1 day) │ │ Pattern (1-2 days) │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ VIEW-000: Foundation (Graph utils, View Switcher, etc.) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ PHASE 2: First Views (Parallel) │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ VIEW-001: │ │ VIEW-002: │ │ VIEW-004: │ │
│ │ Topology Map │ │ X-Ray │ │ Census │ │
│ │ (Meta View) │ │ (Sidebar Panel) │ │ (Sidebar Panel) │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ │
│ These don't need canvas overlays - can build immediately │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ PHASE 3: Canvas Integration │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ PREREQ-004: Canvas Highlighting API │ │
│ │ (1-2 days) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ VIEW-007: │ │ VIEW-005: │ │ VIEW-006: │ │
│ │ Semantic Layers │ │ Data Lineage │ │ Impact Radar │ │
│ │ (Canvas Overlay) │ │ (+ highlighting) │ │ (+ highlighting) │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ PHASE 4: Advanced │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ VIEW-003: Trigger Chain Debugger │ │
│ │ (Needs React 19 debug fixes + runtime integration) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## The Good News: CommentLayer is Our Template!
Looking at `commentlayer.ts`, we already have a working React overlay on the canvas. Key patterns:
```typescript
// CommentLayer creates React elements that float over the canvas
// and respond to pan/zoom
class CommentLayer {
// Uses createRoot (needs the React 19 fix for reuse)
root: Root;
// Responds to viewport changes
_renderReact() {
this.root.render(
<CommentLayerView
scale={this.editor.scale}
pan={this.editor.pan}
// ... passes canvas transforms to React
/>
);
}
}
```
We can build on this pattern for all canvas overlays!
---
## Effort Summary
| Task | Estimate | Cumulative |
|------|----------|------------|
| PREREQ-001: Webpack fix | 1-2 days | 1-2 days |
| PREREQ-002: React 19 fixes | 0.5-1 day | 1.5-3 days |
| PREREQ-003: Document overlays | 1-2 days | 2.5-5 days |
| PREREQ-004: Highlighting API | 1-2 days | 3.5-7 days |
| PREREQ-005: ComponentsPanel | 2-3 days | 5.5-10 days (parallel) |
| **Total Prerequisites** | **~5-7 days** | (with parallelization) |
Then the views themselves: **25-34 days**
**Grand Total: ~30-41 days** for the complete visualization views system.
---
## Prerequisite Task Documents
Each prerequisite has a detailed implementation guide:
- **[PREREQ-001: Webpack Caching Fix](./PREREQ-001-webpack-caching/README.md)** - CRITICAL, do first
- **[PREREQ-002: React 19 Debug Fixes](./PREREQ-002-react19-debug-fixes/README.md)** - Fix legacy ReactDOM
- **[PREREQ-003: Canvas Overlay Pattern](./PREREQ-003-canvas-overlay-pattern/README.md)** - Study CommentLayer
- **[PREREQ-004: Canvas Highlighting API](./PREREQ-004-highlighting-api/README.md)** - Persistent highlights
---
## Files to Reference
- `dev-docs/future-projects/CANVAS-MODERNISATION-PROJECT.md` - Full canvas modernization vision
- `packages/noodl-editor/src/editor/src/views/nodegrapheditor/commentlayer.ts` - Working overlay pattern
- `dev-docs/tasks/phase-2/TASK-004B-componentsPanel-react-migration/STATUS-BLOCKED.md` - Webpack caching issue details
- `dev-docs/tasks/phase-2/TASK-002-react19-ui-fixes/README.md` - React 19 migration issues
- `packages/noodl-editor/src/editor/src/contexts/NodeGraphContext/NodeGraphContext.tsx` - React context for navigation