mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-12 07:12:54 +01:00
Finished component sidebar updates, with one small bug remaining and documented
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
430
dev-docs/tasks/phase-4-canvas-visualisation-views/README.md
Normal file
430
dev-docs/tasks/phase-4-canvas-visualisation-views/README.md
Normal 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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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?
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user