20 KiB
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:
- Shows everywhere a component/node is used
- Shows what depends on specific outputs
- Warns about breaking changes
- Lets you preview "what if I change this?"
- Provides a checklist of things to update
User Stories
-
As a developer refactoring, I want to know everywhere this component is used before I change its interface.
-
As a developer renaming, I want to see all places that reference this name so I don't break anything.
-
As a developer removing, I want to know if anything depends on this before I delete it.
-
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)
- Build on VIEW-000's
findComponentUsages() - Add detailed connection analysis per usage
- Track which ports are connected where
- Count transitive dependencies
Verification:
- Finds all component usages
- Connection details accurate
- Transitive counts correct
Phase 2: Impact Calculation (0.5-1 day)
- Calculate impact level per port
- Analyze property access patterns
- Generate overall impact summary
- Create impact reasons
Verification:
- Impact levels sensible
- Property analysis works
- Summary helpful
Phase 3: UI - Usage Display (1 day)
- Create
ImpactRadarViewcomponent - Show usage locations with details
- Display per-port impact
- Add navigation to usages
Verification:
- Usages displayed clearly
- Port impacts visible
- Navigation works
Phase 4: Checklist Generation (0.5 day)
- Generate change checklist from analysis
- Allow marking items complete
- Prioritize checklist items
Verification:
- Checklist comprehensive
- Priorities sensible
- Can mark complete
Phase 5: Polish & Integration (0.5-1 day)
- Add impact badges to Components Panel
- Context menu "Show Impact"
- Quick impact preview on hover
- 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)