Files
OpenNoodl/dev-docs/tasks/phase-4-canvas-visualisation-views/VIEW-006-impact-radar/README.md

20 KiB
Raw Blame History

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

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

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)