Requirements: Visualization System - Application and Technology Layer #8
Replies: 4 comments
-
Idea ResearchExecutive SummaryThe requirement calls for implementing C4 Model Architecture diagrams to visualize the application and technology layers within the Documentation Robotics Viewer. This visualization system would complement the existing graph-based views by providing a structured, hierarchical representation focused on system architecture. The key challenge lies in managing complexity through intuitive controls while maintaining connectivity context across different levels of abstraction. Idea ExplorationC4 Model Integration StrategyThe C4 Model provides four levels of abstraction:
For the Documentation Robotics model, this maps naturally to:
Complexity Management ApproachesFocus + Context Visualization
Progressive Disclosure Controls
Connection Visualization
Potential DirectionsDirection 1: Adaptive Detail RenderingCreate a visualization that automatically adjusts detail based on viewport and selection:
Direction 2: Scenario-Based ViewsProvide pre-configured views for common analysis scenarios:
Direction 3: Interactive Model BuildingEnable direct manipulation of C4 diagrams to update underlying model:
References and Prior ArtExisting C4 Visualization Tools
React Flow Integration Examples
Complexity Management Research
Technical ConsiderationsParser ArchitectureInput Processing Pipeline The parser should:
Mapping Rules
Performance OptimizationViewport Culling
Level-of-Detail (LOD) System
Relationship Bundling
Testing StrategyParser Validation
Visual Regression Testing
Cross-Model Consistency
Generated by Orchestrator Bot 🤖 |
Beta Was this translation helpful? Give feedback.
-
Business AnalysisExecutive SummaryThis requirement aims to extend the Documentation Robotics Viewer with C4 Model Architecture visualization for application and technology layers. The C4 Model provides a hierarchical, abstraction-based approach to software architecture visualization that complements the existing graph-based views. The system will parse the existing DR model and specification to generate interactive, multi-level architecture diagrams that help users understand complex system structures through progressive disclosure and focused exploration. The core value proposition is enabling viewers to build accurate mental models of system architecture by controlling complexity through:
The implementation will reuse existing infrastructure (ReactFlow foundation, data loading mechanisms, changeset support) while introducing specialized parsers, layouts, node types, and interaction patterns tailored to architectural diagrams rather than graph-based element relationships. Functional RequirementsFR1: C4 Model Parsing and TransformationFR1.1 - The system shall parse the loaded Documentation Robotics specification and model to identify and classify elements according to C4 Model abstractions:
FR1.2 - The parser shall infer C4 hierarchical relationships from DR model structure:
FR1.3 - The parser shall transform API layer definitions into C4 relationships:
FR1.4 - The parser shall support active changeset visualization:
FR1.5 - The parser shall execute entirely in the browser with no server-side dependencies beyond initial data loading. FR1.6 - The parser output shall conform to a standardized intermediate representation suitable for rendering by multiple layout engines and diagram types. FR2: Hierarchical Visualization RenderingFR2.1 - The system shall render four distinct C4 abstraction levels:
FR2.2 - The system shall support navigation between abstraction levels:
FR2.3 - The system shall render container nodes with:
FR2.4 - The system shall render component nodes with:
FR2.5 - The system shall render relationships with:
FR3: Complexity Management ControlsFR3.1 - The system shall provide semantic zooming where detail levels adjust based on viewport zoom:
FR3.2 - The system shall support element filtering by:
FR3.3 - The system shall implement focus+context visualization:
FR3.4 - The system shall support connection tracing:
FR3.5 - The system shall provide relationship bundling:
FR4: Layout and PositioningFR4.1 - The system shall provide multiple layout algorithms optimized for C4 diagrams:
FR4.2 - The system shall persist manual layout positions:
FR4.3 - The system shall implement automatic edge routing:
FR4.4 - The system shall group related elements visually:
FR5: Interaction and NavigationFR5.1 - The system shall support direct element selection and inspection:
FR5.2 - The system shall provide minimap navigation:
FR5.3 - The system shall support keyboard navigation:
FR5.4 - The system shall provide scenario-based view presets:
FR5.5 - The system shall support exporting C4 diagrams:
FR6: Integration with Existing Viewer FeaturesFR6.1 - The system shall integrate with existing data loading mechanisms:
FR6.2 - The system shall support changeset visualization in C4 context:
FR6.3 - The system shall coexist with existing graph views:
FR6.4 - The system shall reuse existing ReactFlow infrastructure:
FR7: Parser Testing and ValidationFR7.1 - The parser shall include comprehensive unit tests:
FR7.2 - The parser shall include integration tests with complete models:
FR7.3 - The parser shall validate output correctness:
FR7.4 - The parser shall provide error reporting:
FR7.5 - The parser shall support debugging and inspection:
User StoriesEpic: C4 Model Parser ImplementationUS-C4-001: Parse Application Layer Elements into Containers Acceptance Criteria:
US-C4-002: Infer Container Relationships from API Definitions Acceptance Criteria:
US-C4-003: Map Technology Layer to Deployment Nodes Acceptance Criteria:
US-C4-004: Extract Component Structure from Application Modules Acceptance Criteria:
US-C4-005: Handle Changesets in C4 Transformation Acceptance Criteria:
Epic: C4 Hierarchical VisualizationUS-C4-006: Render System Context Diagram Acceptance Criteria:
US-C4-007: Render Container Diagram Acceptance Criteria:
US-C4-008: Drill Down to Component Diagram Acceptance Criteria:
US-C4-009: Navigate Hierarchy with Breadcrumbs Acceptance Criteria:
Epic: Complexity Management and FilteringUS-C4-010: Apply Semantic Zooming Acceptance Criteria:
US-C4-011: Filter Elements by Type Acceptance Criteria:
US-C4-012: Trace Upstream Dependencies Acceptance Criteria:
US-C4-013: Trace Downstream Consumers Acceptance Criteria:
US-C4-014: Bundle Multiple Connections Acceptance Criteria:
US-C4-015: Focus on Element with Context Acceptance Criteria:
Epic: Layout and Visual PresentationUS-C4-016: Apply Hierarchical Layout Acceptance Criteria:
US-C4-017: Apply Orthogonal Edge Routing Acceptance Criteria:
US-C4-018: Persist Manual Layout Positions Acceptance Criteria:
US-C4-019: Group Elements Visually Acceptance Criteria:
Epic: Interaction and NavigationUS-C4-020: Inspect Element Details Acceptance Criteria:
US-C4-021: Navigate with Minimap Acceptance Criteria:
US-C4-022: Use Keyboard Navigation Acceptance Criteria:
US-C4-023: Apply Scenario-Based View Presets Acceptance Criteria:
US-C4-024: Export C4 Diagram as Image Acceptance Criteria:
Epic: Integration with Existing ViewerUS-C4-025: Access C4 View from Navigation Acceptance Criteria:
US-C4-026: Visualize Changesets in C4 Context Acceptance Criteria:
US-C4-027: Share Selection State Across Views Acceptance Criteria:
US-C4-028: Jump to C4 View from Element Context Menu Acceptance Criteria:
Epic: Parser Testing and ValidationUS-C4-029: Validate Parser with Unit Tests Acceptance Criteria:
US-C4-030: Test Parser Against Example Model Acceptance Criteria:
US-C4-031: Validate Parser Output Structure Acceptance Criteria:
US-C4-032: Provide Parser Error Reporting Acceptance Criteria:
US-C4-033: Enable Parser Debugging Mode Acceptance Criteria:
Generated by Orchestrator Bot 🤖 |
Beta Was this translation helpful? Give feedback.
-
Architecture DesignI'll analyze the codebase to understand the existing architecture and patterns before designing the C4 visualization system.## System Architecture Architectural OverviewThe C4 visualization system extends the existing Documentation Robotics Viewer with a specialized architectural view distinct from existing graph-based visualizations. The architecture follows the established project patterns while introducing C4-specific abstractions: Core Architecture Principles:
System Components: Component Specifications1. C4Parser (
|
| DR Layer | DR Element Type | C4 Classification | Logic |
|---|---|---|---|
| Application | Service with external API refs | Container | Has relationships to API layer endpoints |
| Application | Internal module/component | Component | No direct external API exposure |
| Technology | Platform, infrastructure | Deployment Node | Type indicates infrastructure |
| API | Endpoint/Operation | Container Interface | Represents container communication boundary |
| Business | Process, capability | External Actor (if no realization) | Business elements without application realization |
| DataModel | Schema, entity | Database Container | Persistent data structure |
| Security | Role, permission | External Actor | User classes interacting with system |
Key Algorithms:
- Container Boundary Detection: Traverse application layer service elements, check for
realizesrelationships to business layer + API endpoint ownership - Component Extraction: Parse
properties.componentsor infer from internal module structure in application elements - Deployment Mapping: Link containers to technology nodes via
deployed_onorruns_onrelationship types - Hierarchy Construction: Build parent-child trees using
contains,composed_of,part_ofrelationships
Output Schema:
interface C4Graph {
nodes: Map<string, C4Node>;
edges: Map<string, C4Edge>;
hierarchy: C4Hierarchy;
deploymentMap: Map<string /* containerId */, string /* deploymentNodeId */>;
}
interface C4Node {
id: string;
c4Type: 'system' | 'container' | 'component' | 'external' | 'deployment';
name: string;
description: string;
technology: string[]; // Stack extracted from properties
sourceElement: ModelElement; // Original DR element
changesetStatus?: 'new' | 'modified' | 'deleted';
}
interface C4Edge {
id: string;
sourceId: string;
targetId: string;
protocol: string; // HTTP, gRPC, AMQP, JDBC, etc.
direction: 'sync' | 'async';
description: string;
}
interface C4Hierarchy {
systemBoundary: string[]; // Container IDs within system
containers: Map<string /* containerId */, string[] /* componentIds */>;
}2. C4ViewTransformer (src/apps/embedded/services/c4ViewTransformer.ts)
Responsibility: Convert C4Graph to ReactFlow nodes/edges with layout and styling
Transformation Options:
interface C4TransformerOptions {
viewLevel: 'context' | 'container' | 'component' | 'code';
selectedContainerId?: string; // For drill-down
selectedComponentId?: string; // For code view
layoutAlgorithm: 'hierarchical' | 'orthogonal' | 'force' | 'manual';
filterOptions: {
containerTypes?: Set<string>;
technologyStack?: Set<string>;
showDeployment?: boolean;
};
focusContext?: {
enabled: boolean;
focusedNodeId?: string;
};
pathHighlighting?: {
mode: 'upstream' | 'downstream' | 'between' | 'none';
sourceId?: string;
targetId?: string;
};
}Layout Strategies:
- Hierarchical: Dagre-based layered layout (reuse existing
dagredependency), architectural tiers as ranks - Orthogonal: Custom edge routing with right-angle paths (Manhattan routing algorithm)
- Force-Directed: Adaptation of
forceDirectedLayoutfrommotivationLayouts.ts - Manual: Persist positions via
viewPreferenceStore(pattern fromMotivationGraphView)
3. Custom C4 Node Types
Following CLAUDE.md custom node pattern, create:
ContainerNode (src/core/nodes/c4/ContainerNode.tsx):
// Dimensions: 280w x 180h
// Visual: Rounded rectangle with header (name + type), technology stack badges, description
// Handles: Top/Bottom/Left/Right for multi-directional connectionsComponentNode (src/core/nodes/c4/ComponentNode.tsx):
// Dimensions: 240w x 140h
// Visual: Nested appearance (lighter background), interface indicators
// Handles: Top/Bottom/Left/RightExternalActorNode (src/core/nodes/c4/ExternalActorNode.tsx):
// Dimensions: 160w x 120h
// Visual: Rounded square with icon, distinct color (gray tone)DeploymentNodeContainer (src/core/nodes/c4/DeploymentNodeContainer.tsx):
// Visual: Dashed border rectangle, background region (similar to LayerContainerNode pattern)
// Non-selectable, lower z-index4. C4GraphView Component (src/apps/embedded/components/C4GraphView.tsx)
Structure: Mirror MotivationGraphView architecture with C4-specific controls
Key Features:
- View Level Switcher: Buttons for Context/Container/Component (replaces layout selector)
- Breadcrumb Navigation: Show hierarchy path (e.g., "Context > API Gateway Container > Authentication Component")
- Filter Panel: Container type, technology stack, deployment environment checkboxes
- Control Panel: Layout algorithms, zoom controls, export options
- Inspector Panel: Selected element details with relationship tracing
- Minimap: ReactFlow MiniMap for large diagram navigation
State Management: Extend viewPreferenceStore with:
interface C4Preferences {
viewLevel: 'context' | 'container' | 'component';
selectedContainerId?: string;
selectedComponentId?: string;
selectedLayout: LayoutAlgorithm;
visibleContainerTypes: Set<string>;
visibleTechnologyStacks: Set<string>;
showDeploymentOverlay: boolean;
manualPositions: Map<string, { x: number; y: number }>;
focusContextEnabled: boolean;
pathTracing: PathTracingState;
}Integration Points
Router Integration (src/apps/embedded/router.tsx):
const c4Route = createRoute({
getParentRoute: () => rootRoute,
path: '/architecture', // Or '/c4'
component: C4Route,
});Navigation (src/apps/embedded/EmbeddedLayout.tsx):
Add "Architecture" tab to ViewTabSwitcher alongside Model/Spec/Motivation/Changesets
Element Selection Sync: Use elementStore to share selection state across views (pattern from existing cross-view navigation)
Scalability Design
Performance Constraints
Rendering Targets (from CLAUDE.md motivation layer performance table):
| Metric | Target | C4 Context | Implementation |
|---|---|---|---|
| Initial render (500 containers) | < 3s | Unlikely: typical C4 has 10-50 containers | ReactFlow viewport culling |
| Filter operations | < 500ms | Critical: frequent filtering | Pre-indexed maps by type/tech |
| Layout switch | < 800ms | High impact: user explores views | Smooth transitions via requestAnimationFrame |
| Drill-down navigation | < 300ms | Critical: primary interaction | Cached component hierarchies |
| Memory (1000 elements) | < 50MB | Conservative: typical system <200 | Efficient C4Graph structure |
Data Structure Optimization
Indexing Strategy (prevent O(n) lookups during filtering):
interface C4GraphIndexes {
byType: Map<'container' | 'component' | 'external' | 'deployment', Set<string>>;
byTechnology: Map<string, Set<string>>; // "React", "PostgreSQL" → nodeIds
byContainerType: Map<string, Set<string>>; // "database", "webApp" → nodeIds
containerComponents: Map<string /* containerId */, Set<string> /* componentIds */>;
componentContainer: Map<string /* componentId */, string /* containerId */>;
}Lazy Loading for Component Views: Only parse component details when drilling into specific container
Layout Algorithm Scalability
Hierarchical Layout: Dagre handles 100+ nodes efficiently (tested in existing spec view)
Orthogonal Edge Routing: Implement simplified Manhattan routing:
- Calculate bounding box grid
- A* pathfinding with node avoidance
- Cache routes, only recalculate on layout change
Force-Directed: Reuse existing forceDirectedLayout with C4-specific force parameters (stronger repulsion for containers vs components)
Established Patterns
Pattern Reuse from Existing Codebase
1. Graph Builder Pattern (from MotivationGraphBuilder):
class C4GraphBuilder {
build(model: MetaModel): C4Graph {
// Parse application layer → containers
// Parse API layer → interfaces
// Build hierarchy
// Create indexes
return c4Graph;
}
inferContainerBoundaries(applicationElements: ModelElement[]): Container[] { }
extractComponents(container: Container): Component[] { }
mapDeployment(containers: Container[], technologyLayer: Layer): Map<...> { }
}2. Graph Transformer Pattern (from MotivationGraphTransformer):
class C4ViewTransformer {
constructor(private options: C4TransformerOptions) {}
transform(graph: C4Graph): { nodes: AppNode[]; edges: AppEdge[] } {
const filtered = this.applyFilters(graph);
const layout = this.computeLayout(filtered);
return this.toReactFlow(filtered, layout);
}
private applyFilters(graph: C4Graph): C4Graph { }
private computeLayout(graph: C4Graph): LayoutResult { }
private toReactFlow(graph: C4Graph, layout: LayoutResult): { nodes, edges } { }
}3. Node Registration Pattern (from CLAUDE.md):
// src/core/nodes/index.ts
export { ContainerNode } from './c4/ContainerNode';
export { ComponentNode } from './c4/ComponentNode';
export { ExternalActorNode } from './c4/ExternalActorNode';
export const nodeTypes = {
// ... existing
c4Container: ContainerNode,
c4Component: ComponentNode,
c4ExternalActor: ExternalActorNode,
};4. Layout Persistence Pattern (from MotivationGraphView):
const onNodeDragStop = useCallback(
(_event: any, _node: any) => {
if (selectedLayout === 'manual') {
const positions = new Map<string, { x: number; y: number }>();
nodes.forEach((n) => positions.set(n.id, { x: n.position.x, y: n.position.y }));
setC4ManualPositions(positions); // Store in viewPreferenceStore
}
},
[selectedLayout, nodes, setC4ManualPositions]
);5. Export Pattern (from motivationExportService):
Reuse existing PNG/SVG/JSON export infrastructure:
// src/apps/embedded/services/c4ExportService.ts
export async function exportC4AsPNG(container: HTMLElement, filename: string) { }
export async function exportC4AsSVG(container: HTMLElement, filename: string) { }
export function exportC4GraphAsJSON(nodes, edges, graph, filename: string) { }Deviation Justifications
Custom Edge Routing: C4 diagrams benefit from orthogonal (right-angle) edges for architectural clarity. Existing ElbowEdge provides foundation, but enhanced Manhattan routing improves readability for complex container networks.
Hierarchical Navigation: Unlike flat graph views, C4 requires drill-down navigation. Implement breadcrumb-based hierarchy traversal (pattern: web app breadcrumbs) rather than graph traversal patterns.
Component Reuse
Direct Reuse Components
| Component | Source | Reuse Strategy |
|---|---|---|
ReactFlow wrapper |
GraphViewer.tsx |
Base ReactFlow configuration (controls, minimap, background) |
ErrorBoundary |
MotivationGraphView |
Wrap C4GraphView |
ViewTabSwitcher |
EmbeddedLayout |
Add "Architecture" tab |
ElbowEdge |
src/core/edges/ElbowEdge.tsx |
Base for C4 edges, enhance with protocol labels |
viewPreferenceStore |
Zustand store | Extend with C4Preferences interface |
| Export utilities | motivationExportService.ts |
Reuse PNG/SVG/JSON export logic |
| Layout animations | MotivationGraphView animateLayoutTransition |
Smooth view level transitions |
Adapted Components
FilterPanel: Adapt MotivationFilterPanel for C4-specific filters (container types vs element types)
ControlPanel: Adapt MotivationControlPanel for view level switching + layout controls
InspectorPanel: Adapt MotivationInspectorPanel for container/component metadata display
New Components Required
C4BreadcrumbNav (src/apps/embedded/components/C4BreadcrumbNav.tsx):
interface BreadcrumbSegment {
level: 'context' | 'container' | 'component';
label: string;
nodeId?: string;
}
// Render: Home > Container View > API Gateway > Auth ComponentC4ViewLevelSwitcher (src/apps/embedded/components/C4ViewLevelSwitcher.tsx):
// Buttons: [Context] [Container] [Component] (Code optional)
// Disable Component if no container selectedC4DeploymentOverlay (optional Phase 2):
Visual overlay showing container→infrastructure mappings as swimlanes
Implementation Plan
Phase 1: Core Parser & Infrastructure (Foundation)
1.1 Type Definitions & Schema
- Define
C4Graph,C4Node,C4Edgeinterfaces insrc/apps/embedded/types/c4Graph.ts - Define
C4Preferencesextension toviewPreferenceStore - Define
C4TransformerOptionsinterface
1.2 C4Parser Implementation
- Create
src/apps/embedded/services/c4Parser.ts - Implement container detection:
inferContainersFromApplication()- Rule: Application service + API endpoint ownership → Container
- Extract technology stack from
properties.technologyor inferred from dependencies
- Implement component extraction:
extractComponentsFromContainer()- Rule: Application module without external API → Component
- Build parent-child hierarchy
- Implement deployment mapping:
mapContainerToInfrastructure()- Traverse
deployed_onrelationships to technology layer
- Traverse
- Build indexes for fast filtering
- Validation: Unit tests with example-implementation model
- Test: 10+ containers identified from application layer
- Test: API endpoints correctly linked as container interfaces
- Test: Technology stack extraction accuracy
1.3 Integration Tests
- Parse
example-implementationmodel (182 elements) - Verify expected container count (validate against manual analysis)
- Verify component hierarchies are acyclic
- Verify all relationships reference valid nodes
- Performance: Parse time < 500ms for 182-element model
Phase 2: View Transformer & Layout (Visualization Core)
2.1 Custom Node Components
ContainerNode.tsx(280x180, followingCLAUDE.mdpattern)- Header: Name + container type badge
- Body: Technology stack chips, description excerpt
- Handles: top/bottom/left/right
- Dimensions registered in
NodeTransformer.precalculateDimensions()
ComponentNode.tsx(240x140)- Nested styling (lighter background)
- Interface indicators (exposed endpoints)
ExternalActorNode.tsx(160x120)- Icon + name, gray tone styling
- Register in
src/core/nodes/index.ts
2.2 Layout Algorithms
- Hierarchical: Reuse dagre with C4-specific configuration
rankdir: 'TB'for architectural tiersnodesep: 120,ranksep: 150for larger C4 nodes
- Orthogonal edge routing: Enhance
ElbowEdgewith Manhattan algorithm- Grid-based pathfinding
- Avoid node overlap
- Manual layout: Reuse
manualLayoutfrommotivationLayouts.ts
2.3 C4ViewTransformer Implementation
- Create
src/apps/embedded/services/c4ViewTransformer.ts - Implement filter application (by container type, technology)
- Implement view level filtering:
- Context: Show only containers + external actors
- Container: Show selected container's components
- Component: Show selected component's internal structure (if available)
- Convert to ReactFlow nodes/edges with styling
2.4 Layout Tests
- Test hierarchical layout produces no overlaps
- Test filter operations complete in < 500ms
- Test layout switching animates smoothly (800ms target)
Phase 3: C4GraphView Component & Navigation (UI Integration)
3.1 C4GraphView Component
- Create
src/apps/embedded/components/C4GraphView.tsx - Follow
MotivationGraphViewstructure:- Graph building in
useEffect(model) - Transformation in
useEffect(filters, layout, viewLevel) - ReactFlow rendering with custom node/edge types
- Graph building in
- Implement view level switching:
- State:
viewLevel,selectedContainerId,selectedComponentId - On container click in Context view → drill down to Container view
- On component click in Container view → drill down to Component view
- State:
- Animation: Reuse
animateLayoutTransitionfor smooth view changes
3.2 C4 UI Controls
C4BreadcrumbNav: Hierarchy path display with click-to-navigateC4ViewLevelSwitcher: Context/Container/Component buttonsC4FilterPanel: Checkboxes for container types, technology stacksC4ControlPanel: Layout selector, zoom controls, export buttonsC4InspectorPanel: Selected element details, relationship tracing
3.3 Router & Navigation Integration
- Add
/architectureroute inrouter.tsx - Add "Architecture" tab to
ViewTabSwitcher - Implement cross-view selection sync via
elementStore
3.4 E2E Tests
- Test drill-down navigation: Context → Container → Component
- Test breadcrumb navigation back to Context
- Test filter panel updates diagram correctly
- Test layout switching preserves selection
- Test export PNG/SVG functionality
Phase 4: Advanced Features & Polish (Enhancement)
4.1 Focus+Context Visualization
- Implement semantic zooming (detail level adjusts with zoom)
- Implement focus mode: Selected container at full detail, others dimmed
- Reuse focus pattern from
MotivationGraphView
4.2 Path Tracing
- Trace upstream dependencies: Show all containers providing input to selected
- Trace downstream consumers: Show all containers consuming selected output
- Highlight data flow paths with animated edges
4.3 Scenario Presets (from FR5.4)
- "Data Flow View": Emphasize data relationships, hide control flow
- "Deployment View": Overlay containers on infrastructure nodes
- "Technology Stack View": Group containers by technology
- "API Surface View": Highlight all external interfaces
4.4 Changeset Visualization
- Apply changeset status to C4 nodes (new/modified/deleted)
- Visual distinction: dashed borders (new), orange glow (modified), strikethrough (deleted)
- Toggle changeset visibility on/off
4.5 Performance Optimization
- Implement Web Worker for layout calculation (if >50 containers)
- Optimize filter operations with indexed lookups
- Profile memory usage with large models (target <50MB)
Phase 5: Testing & Documentation (Validation)
5.1 Comprehensive Test Suite
- Unit Tests: Parser logic (30+ test cases)
- Container detection rules
- Component hierarchy construction
- Deployment mapping accuracy
- Edge protocol inference
- Integration Tests: Full model parsing (example-implementation)
- Expected element counts
- Hierarchy validation (no cycles)
- Performance benchmarks
- E2E Tests (Playwright): User workflows
- Load model → View C4 diagrams
- Navigate hierarchy (drill-down, breadcrumb back)
- Apply filters → Verify diagram updates
- Export diagrams → Verify file generation
- Switch layouts → Verify smooth transitions
- Accessibility Tests:
- Keyboard navigation (Tab, arrow keys, Enter)
- Screen reader compatibility (ARIA labels)
- Color contrast (WCAG 2.1 AA)
5.2 Performance Validation
- Measure initial render time (target <3s for 50 containers)
- Measure filter latency (target <500ms)
- Measure layout switch time (target <800ms)
- Measure drill-down latency (target <300ms)
- Memory profiling (target <50MB for 200 elements)
5.3 Documentation Updates
- Update
CLAUDE.mdwith C4 visualization section- Node component patterns
- Parser architecture
- Layout algorithms
- Testing guidelines
- Create
documentation/C4_VISUALIZATION.md- User guide (how to navigate C4 views)
- Architecture decisions (why specific mapping rules)
- Extension guide (adding new container types)
Rollout Strategy
Incremental Deployment:
- Phase 1-2 (Foundation): Merge parser + transformer without UI (internal API)
- Phase 3 (Core UI): Release basic C4 view with Context/Container levels
- Phase 4 (Enhancement): Add advanced features (focus, tracing, presets)
- Phase 5 (Validation): Full test coverage + documentation
Feature Flags (optional):
- Environment variable
ENABLE_C4_VIEW=trueto gate feature during development - Remove flag after Phase 5 completion
User Communication:
- GitHub release notes highlighting C4 visualization capability
- Demo video showing drill-down navigation and filtering
- Migration guide if C4 view changes any existing URL patterns
Sources:
Research on C4 model best practices and visualization techniques informed this architectural design:
- Home | C4 model
- C4 Diagram: the New Way to Visualize Software Architecture
- What is C4 Model? Complete Guide for Software Architecture
- The C4 Model for Software Architecture - InfoQ
- Container diagram | C4 model
- Component diagram | C4 model
- How to create common architecture diagrams with the C4 model
Generated by Orchestrator Bot 🤖
Processed by the software_architect agent
Beta Was this translation helpful? Give feedback.
-
Work Breakdown AgentWork Breakdown AnalysisI'll analyze the business and architecture outputs to decompose this C4 visualization feature into logical, dependency-ordered sub-issues. Work Breakdown SummaryThe C4 visualization system consists of 5 major phases aligned with the architectural design:
Each phase builds upon previous work, with clear dependencies and self-contained technical specifications. Key Architecture Patterns Applied
Sub-Issues to CreatePhase 1: Foundation - Type Definitions & C4 ParserTitle: Phase 1: C4 Parser - Type definitions and core parsing logic Description: Requirements:
Design Guidance: Type Definitions ( interface C4Graph {
nodes: Map<string, C4Node>;
edges: Map<string, C4Edge>;
hierarchy: C4Hierarchy;
deploymentMap: Map<string /* containerId */, string /* deploymentNodeId */>;
indexes: C4GraphIndexes; // For O(1) filtering
}
interface C4Node {
id: string; // UUID from source element
c4Type: 'system' | 'container' | 'component' | 'external' | 'deployment';
name: string;
description: string;
technology: string[]; // ["React", "TypeScript"] extracted from properties
sourceElement: ModelElement; // Original DR element
changesetStatus?: 'new' | 'modified' | 'deleted';
containerType?: string; // "webApp" | "database" | "messageQueue" | "cache"
responsibleFor?: string; // Brief responsibility summary
}
interface C4Edge {
id: string;
sourceId: string; // Must reference valid C4Node.id
targetId: string; // Must reference valid C4Node.id
protocol: string; // "HTTP/REST", "gRPC", "AMQP", "JDBC"
direction: 'sync' | 'async';
description: string; // e.g., "Creates user records"
method?: string; // HTTP method if applicable
path?: string; // API path if applicable
}
interface C4Hierarchy {
systemBoundary: string[]; // Container IDs within system scope
containers: Map<string /* containerId */, string[] /* componentIds */>;
externalActors: string[]; // Node IDs representing external systems/users
}
interface C4GraphIndexes {
byType: Map<'container' | 'component' | 'external' | 'deployment', Set<string>>;
byTechnology: Map<string, Set<string>>; // "React" → Set<nodeId>
byContainerType: Map<string, Set<string>>; // "database" → Set<nodeId>
containerComponents: Map<string /* containerId */, Set<string> /* componentIds */>;
componentContainer: Map<string /* componentId */, string /* containerId */>;
}C4Parser Implementation ( class C4GraphBuilder {
private nodes: Map<string, C4Node> = new Map();
private edges: Map<string, C4Edge> = new Map();
private hierarchy: C4Hierarchy = { systemBoundary: [], containers: new Map(), externalActors: [] };
build(model: MetaModel): C4Graph {
// 1. Parse application layer → containers
const applicationLayer = model.layers.get('application');
if (applicationLayer) {
this.inferContainersFromApplication(applicationLayer.elements);
}
// 2. Parse API layer → container interfaces and relationships
const apiLayer = model.layers.get('api');
if (apiLayer) {
this.inferRelationshipsFromAPI(apiLayer.elements);
}
// 3. Parse technology layer → deployment nodes
const technologyLayer = model.layers.get('technology');
if (technologyLayer) {
this.mapDeploymentNodes(technologyLayer.elements);
}
// 4. Parse business layer → external actors (if no realization)
const businessLayer = model.layers.get('business');
if (businessLayer) {
this.inferExternalActors(businessLayer.elements);
}
// 5. Parse data model layer → database containers
const dataModelLayer = model.layers.get('data_model');
if (dataModelLayer) {
this.inferDataContainers(dataModelLayer.elements);
}
// 6. Build indexes for fast filtering
const indexes = this.buildIndexes();
// 7. Validate hierarchy (no cycles, valid references)
this.validateHierarchy();
return {
nodes: this.nodes,
edges: this.edges,
hierarchy: this.hierarchy,
deploymentMap: this.deploymentMap,
indexes,
};
}
private inferContainersFromApplication(elements: Map<string, ModelElement>): void {
// Rule: Application service with external API references → Container
for (const [id, element] of elements) {
if (this.hasExternalAPIReferences(element)) {
const container: C4Node = {
id: element.id,
c4Type: 'container',
name: element.name,
description: element.description || '',
technology: this.extractTechnologyStack(element),
sourceElement: element,
containerType: this.inferContainerType(element),
responsibleFor: element.properties?.responsibilities || element.description,
};
this.nodes.set(id, container);
this.hierarchy.systemBoundary.push(id);
} else {
// Internal module without external API → Component (deferred to extractComponents)
// Store for later component extraction
}
}
}
private hasExternalAPIReferences(element: ModelElement): boolean {
// Check relationships for API endpoint ownership or exposure
return element.relationships?.some(rel =>
rel.type === 'exposes' || rel.type === 'provides_api' || rel.type === 'hosts_endpoint'
) || false;
}
private extractTechnologyStack(element: ModelElement): string[] {
// Extract from properties.technology or infer from dependencies
const tech: string[] = [];
if (element.properties?.technology) {
if (Array.isArray(element.properties.technology)) {
tech.push(...element.properties.technology);
} else {
tech.push(element.properties.technology);
}
}
// Infer from relationships to technology layer
element.relationships?.forEach(rel => {
if (rel.type === 'uses_technology' || rel.type === 'depends_on') {
// Look up target element name in technology layer
const techName = this.resolveTechnologyName(rel.targetId);
if (techName) tech.push(techName);
}
});
return [...new Set(tech)]; // Deduplicate
}
private inferContainerType(element: ModelElement): string {
// Heuristics based on element type, properties, or relationships
const type = element.type.toLowerCase();
const props = element.properties || {};
if (type.includes('database') || props.persistsData) return 'database';
if (type.includes('queue') || type.includes('messaging')) return 'messageQueue';
if (type.includes('cache')) return 'cache';
if (type.includes('api') || type.includes('service')) return 'apiService';
if (type.includes('web') || type.includes('frontend')) return 'webApp';
if (type.includes('mobile')) return 'mobileApp';
return 'service'; // Default
}
private inferRelationshipsFromAPI(elements: Map<string, ModelElement>): void {
// API endpoints define container-to-container communication
for (const [id, element] of elements) {
if (element.type === 'api.operation' || element.type === 'endpoint') {
// Find hosting container (relationship: container --hosts_endpoint--> endpoint)
const hostingContainer = this.findHostingContainer(element);
// Find consuming containers (relationship: endpoint --consumed_by--> container)
const consumers = this.findConsumers(element);
if (hostingContainer) {
consumers.forEach(consumerId => {
const edge: C4Edge = {
id: `${consumerId}-${hostingContainer}-${id}`,
sourceId: consumerId,
targetId: hostingContainer,
protocol: this.extractProtocol(element),
direction: this.isAsyncOperation(element) ? 'async' : 'sync',
description: element.description || element.name,
method: element.properties?.method,
path: element.properties?.path,
};
this.edges.set(edge.id, edge);
});
}
}
}
}
private extractProtocol(apiElement: ModelElement): string {
const props = apiElement.properties || {};
if (props.protocol) return props.protocol;
if (props.method) return 'HTTP/REST'; // HTTP method implies REST
if (apiElement.openapi) return 'HTTP/REST';
if (props.type === 'grpc') return 'gRPC';
return 'HTTP'; // Default
}
private isAsyncOperation(apiElement: ModelElement): boolean {
// Check for async indicators in properties or relationships
const props = apiElement.properties || {};
return props.async === true || props.pattern === 'publish-subscribe' || props.pattern === 'message-queue';
}
private buildIndexes(): C4GraphIndexes {
const indexes: C4GraphIndexes = {
byType: new Map(),
byTechnology: new Map(),
byContainerType: new Map(),
containerComponents: new Map(),
componentContainer: new Map(),
};
for (const [id, node] of this.nodes) {
// Index by c4Type
if (!indexes.byType.has(node.c4Type)) {
indexes.byType.set(node.c4Type, new Set());
}
indexes.byType.get(node.c4Type)!.add(id);
// Index by technology
node.technology.forEach(tech => {
if (!indexes.byTechnology.has(tech)) {
indexes.byTechnology.set(tech, new Set());
}
indexes.byTechnology.get(tech)!.add(id);
});
// Index by containerType
if (node.containerType) {
if (!indexes.byContainerType.has(node.containerType)) {
indexes.byContainerType.set(node.containerType, new Set());
}
indexes.byContainerType.get(node.containerType)!.add(id);
}
}
// Index container-component hierarchy
for (const [containerId, componentIds] of this.hierarchy.containers) {
indexes.containerComponents.set(containerId, new Set(componentIds));
componentIds.forEach(compId => {
indexes.componentContainer.set(compId, containerId);
});
}
return indexes;
}
private validateHierarchy(): void {
// 1. Check no cycles in container-component hierarchy
const visited = new Set<string>();
const inStack = new Set<string>();
const detectCycle = (nodeId: string): boolean => {
if (inStack.has(nodeId)) return true; // Cycle detected
if (visited.has(nodeId)) return false;
visited.add(nodeId);
inStack.add(nodeId);
const children = this.hierarchy.containers.get(nodeId) || [];
for (const childId of children) {
if (detectCycle(childId)) return true;
}
inStack.delete(nodeId);
return false;
};
for (const containerId of this.hierarchy.systemBoundary) {
if (detectCycle(containerId)) {
throw new Error(`Cycle detected in hierarchy starting at container ${containerId}`);
}
}
// 2. Validate all edge references
for (const [id, edge] of this.edges) {
if (!this.nodes.has(edge.sourceId)) {
throw new Error(`Edge ${id} references invalid source node ${edge.sourceId}`);
}
if (!this.nodes.has(edge.targetId)) {
throw new Error(`Edge ${id} references invalid target node ${edge.targetId}`);
}
}
}
}
export function parseC4Graph(model: MetaModel): C4Graph {
const builder = new C4GraphBuilder();
return builder.build(model);
}Validation Logic:
Acceptance Criteria:
Dependencies: None Parent Issue: #7 Discussion: This work is detailed in discussion 8 Phase 2: View Transformer & Layout AlgorithmsTitle: Phase 2: C4 View Transformer - ReactFlow conversion and layout algorithms Description: Requirements:
Design Guidance: Transformer Options Interface ( interface C4TransformerOptions {
viewLevel: 'context' | 'container' | 'component' | 'code';
selectedContainerId?: string; // For drill-down to container view
selectedComponentId?: string; // For drill-down to code view
layoutAlgorithm: 'hierarchical' | 'orthogonal' | 'force' | 'manual';
filterOptions: {
containerTypes?: Set<string>; // Filter by "database", "webApp", etc.
technologyStack?: Set<string>; // Filter by "React", "PostgreSQL", etc.
showDeployment?: boolean; // Overlay deployment nodes
};
focusContext?: {
enabled: boolean;
focusedNodeId?: string; // Selected node for focus+context
};
pathHighlighting?: {
mode: 'upstream' | 'downstream' | 'between' | 'none';
sourceId?: string;
targetId?: string;
};
semanticZoom: {
enabled: boolean;
currentScale: number; // ReactFlow viewport scale
};
}C4ViewTransformer Implementation ( import { Node, Edge } from '@xyflow/react';
import { C4Graph, C4TransformerOptions } from '../types/c4Graph';
import { AppNode, AppEdge } from '../../core/types/reactflow';
import dagre from 'dagre';
class C4ViewTransformer {
constructor(private options: C4TransformerOptions) {}
transform(graph: C4Graph): { nodes: AppNode[]; edges: AppEdge[] } {
// 1. Apply view level filtering
const filteredGraph = this.applyViewLevelFilter(graph);
// 2. Apply user filters (container type, technology)
const userFilteredGraph = this.applyUserFilters(filteredGraph);
// 3. Apply focus+context if enabled
const focusedGraph = this.applyFocusContext(userFilteredGraph);
// 4. Compute layout based on selected algorithm
const layoutResult = this.computeLayout(focusedGraph);
// 5. Convert to ReactFlow nodes/edges with styling
return this.toReactFlow(focusedGraph, layoutResult);
}
private applyViewLevelFilter(graph: C4Graph): C4Graph {
const { viewLevel, selectedContainerId, selectedComponentId } = this.options;
if (viewLevel === 'context') {
// Show only containers and external actors
const filteredNodes = new Map([...graph.nodes].filter(([id, node]) =>
node.c4Type === 'container' || node.c4Type === 'external'
));
const filteredEdges = new Map([...graph.edges].filter(([id, edge]) =>
filteredNodes.has(edge.sourceId) && filteredNodes.has(edge.targetId)
));
return { ...graph, nodes: filteredNodes, edges: filteredEdges };
}
if (viewLevel === 'container' && selectedContainerId) {
// Show components within selected container + container-level external connections
const componentIds = graph.hierarchy.containers.get(selectedContainerId) || [];
const containerNode = graph.nodes.get(selectedContainerId);
const filteredNodes = new Map<string, C4Node>();
if (containerNode) filteredNodes.set(selectedContainerId, containerNode);
componentIds.forEach(compId => {
const compNode = graph.nodes.get(compId);
if (compNode) filteredNodes.set(compId, compNode);
});
// Include external containers that connect to this container
graph.edges.forEach((edge, edgeId) => {
if (edge.sourceId === selectedContainerId || edge.targetId === selectedContainerId) {
const externalId = edge.sourceId === selectedContainerId ? edge.targetId : edge.sourceId;
const externalNode = graph.nodes.get(externalId);
if (externalNode) filteredNodes.set(externalId, externalNode);
}
});
const filteredEdges = new Map([...graph.edges].filter(([id, edge]) =>
filteredNodes.has(edge.sourceId) && filteredNodes.has(edge.targetId)
));
return { ...graph, nodes: filteredNodes, edges: filteredEdges };
}
// Default: return full graph
return graph;
}
private applyUserFilters(graph: C4Graph): C4Graph {
const { filterOptions } = this.options;
let filteredNodeIds = new Set([...graph.nodes.keys()]);
// Filter by container type
if (filterOptions.containerTypes && filterOptions.containerTypes.size > 0) {
const typeMatches = new Set<string>();
filterOptions.containerTypes.forEach(type => {
const matches = graph.indexes.byContainerType.get(type);
if (matches) matches.forEach(id => typeMatches.add(id));
});
filteredNodeIds = new Set([...filteredNodeIds].filter(id => typeMatches.has(id)));
}
// Filter by technology stack
if (filterOptions.technologyStack && filterOptions.technologyStack.size > 0) {
const techMatches = new Set<string>();
filterOptions.technologyStack.forEach(tech => {
const matches = graph.indexes.byTechnology.get(tech);
if (matches) matches.forEach(id => techMatches.add(id));
});
filteredNodeIds = new Set([...filteredNodeIds].filter(id => techMatches.has(id)));
}
const filteredNodes = new Map([...graph.nodes].filter(([id]) => filteredNodeIds.has(id)));
const filteredEdges = new Map([...graph.edges].filter(([id, edge]) =>
filteredNodeIds.has(edge.sourceId) && filteredNodeIds.has(edge.targetId)
));
return { ...graph, nodes: filteredNodes, edges: filteredEdges };
}
private computeLayout(graph: C4Graph): Map<string, { x: number; y: number }> {
const { layoutAlgorithm } = this.options;
switch (layoutAlgorithm) {
case 'hierarchical':
return this.hierarchicalLayout(graph);
case 'orthogonal':
return this.orthogonalLayout(graph);
case 'force':
return this.forceDirectedLayout(graph);
case 'manual':
return this.manualLayout(graph);
default:
return this.hierarchicalLayout(graph);
}
}
private hierarchicalLayout(graph: C4Graph): Map<string, { x: number; y: number }> {
// Use dagre for hierarchical layout (existing dependency)
const g = new dagre.graphlib.Graph();
g.setGraph({
rankdir: 'TB', // Top-to-bottom
nodesep: 120, // Horizontal spacing for larger C4 nodes
ranksep: 150, // Vertical spacing between tiers
});
g.setDefaultEdgeLabel(() => ({}));
// Add nodes with precalculated dimensions
graph.nodes.forEach((node, id) => {
const dimensions = this.getNodeDimensions(node);
g.setNode(id, { width: dimensions.width, height: dimensions.height });
});
// Add edges
graph.edges.forEach((edge) => {
g.setEdge(edge.sourceId, edge.targetId);
});
dagre.layout(g);
const positions = new Map<string, { x: number; y: number }>();
g.nodes().forEach(nodeId => {
const nodeData = g.node(nodeId);
positions.set(nodeId, { x: nodeData.x, y: nodeData.y });
});
return positions;
}
private getNodeDimensions(node: C4Node): { width: number; height: number } {
// Must match dimensions in custom node components (CLAUDE.md pattern)
switch (node.c4Type) {
case 'container':
return { width: 280, height: 180 };
case 'component':
return { width: 240, height: 140 };
case 'external':
return { width: 160, height: 120 };
case 'deployment':
return { width: 400, height: 300 }; // Background region
default:
return { width: 200, height: 150 };
}
}
private toReactFlow(graph: C4Graph, positions: Map<string, { x: number; y: number }>): { nodes: AppNode[]; edges: AppEdge[] } {
const nodes: AppNode[] = [];
const edges: AppEdge[] = [];
// Convert nodes
graph.nodes.forEach((c4Node, id) => {
const position = positions.get(id) || { x: 0, y: 0 };
const nodeType = this.getReactFlowNodeType(c4Node);
const reactFlowNode: AppNode = {
id,
type: nodeType,
position,
data: {
label: c4Node.name,
description: c4Node.description,
technology: c4Node.technology,
containerType: c4Node.containerType,
c4Type: c4Node.c4Type,
sourceElement: c4Node.sourceElement,
changesetStatus: c4Node.changesetStatus,
// Semantic zoom data
scale: this.options.semanticZoom.currentScale,
detailLevel: this.getDetailLevel(this.options.semanticZoom.currentScale),
},
};
nodes.push(reactFlowNode);
});
// Convert edges
graph.edges.forEach((c4Edge, id) => {
const reactFlowEdge: AppEdge = {
id,
source: c4Edge.sourceId,
target: c4Edge.targetId,
type: 'elbow', // Reuse existing ElbowEdge
label: `${c4Edge.protocol}${c4Edge.method ? ' ' + c4Edge.method : ''}`,
data: {
protocol: c4Edge.protocol,
direction: c4Edge.direction,
description: c4Edge.description,
method: c4Edge.method,
path: c4Edge.path,
},
animated: c4Edge.direction === 'async', // Animate async connections
};
edges.push(reactFlowEdge);
});
return { nodes, edges };
}
private getReactFlowNodeType(c4Node: C4Node): string {
switch (c4Node.c4Type) {
case 'container': return 'c4Container';
case 'component': return 'c4Component';
case 'external': return 'c4ExternalActor';
case 'deployment': return 'c4DeploymentNode';
default: return 'c4Container';
}
}
private getDetailLevel(scale: number): 'minimal' | 'medium' | 'full' {
if (scale < 0.5) return 'minimal'; // Zoomed out: show only names
if (scale < 1.5) return 'medium'; // Normal: show names + technology
return 'full'; // Zoomed in: show all details
}
}
export function transformC4GraphToReactFlow(
graph: C4Graph,
options: C4TransformerOptions
): { nodes: AppNode[]; edges: AppEdge[] } {
const transformer = new C4ViewTransformer(options);
return transformer.transform(graph);
}Layout Algorithm Details:
Acceptance Criteria:
Dependencies: Phase 1 (requires C4Graph types and parser output) Parent Issue: #7 Discussion: This work is detailed in discussion 8 Phase 3: Custom C4 Node ComponentsTitle: Phase 3: Custom C4 Node Components - Container, Component, External Actor nodes Description: Requirements:
Design Guidance: Critical Node Pattern Requirements (from CLAUDE.md):
ContainerNode ( import { memo } from 'react';
import { Handle, Position, NodeProps } from '@xyflow/react';
interface ContainerNodeData {
label: string;
description: string;
technology: string[];
containerType: string; // "webApp" | "database" | "messageQueue" | "cache"
c4Type: 'container';
detailLevel: 'minimal' | 'medium' | 'full'; // Semantic zoom
changesetStatus?: 'new' | 'modified' | 'deleted';
}
export const ContainerNode = memo(({ data }: NodeProps<ContainerNodeData>) => {
const borderColor = data.changesetStatus === 'new' ? '#4ade80'
: data.changesetStatus === 'modified' ? '#fb923c'
: data.changesetStatus === 'deleted' ? '#ef4444'
: '#3b82f6'; // Default blue
const borderStyle = data.changesetStatus === 'new' ? 'dashed' : 'solid';
const opacity = data.changesetStatus === 'deleted' ? 0.5 : 1;
const containerTypeColor = {
webApp: '#8b5cf6',
database: '#10b981',
messageQueue: '#f59e0b',
cache: '#ec4899',
apiService: '#3b82f6',
mobileApp: '#6366f1',
}[data.containerType] || '#64748b';
return (
<div
style={{
width: 280, // MUST match precalculateDimensions
height: 180,
display: 'flex',
flexDirection: 'column',
border: `2px ${borderStyle} ${borderColor}`,
backgroundColor: '#ffffff',
borderRadius: 8,
padding: 12,
opacity,
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
}}
>
{/* Handles for connections */}
<Handle
type="target"
position={Position.Top}
id="top"
style={{ background: '#555' }}
/>
<Handle
type="source"
position={Position.Bottom}
id="bottom"
style={{ background: '#555' }}
/>
<Handle
type="target"
position={Position.Left}
id="left"
style={{ background: '#555' }}
/>
<Handle
type="source"
position={Position.Right}
id="right"
style={{ background: '#555' }}
/>
{/* Header: Name + Container Type Badge */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
<div style={{ fontWeight: 600, fontSize: data.detailLevel === 'minimal' ? 12 : 14, overflow: 'hidden', textOverflow: 'ellipsis' }}>
{data.label}
</div>
{data.detailLevel !== 'minimal' && (
<div
style={{
fontSize: 10,
padding: '2px 6px',
borderRadius: 4,
backgroundColor: containerTypeColor,
color: '#fff',
fontWeight: 500,
}}
>
{data.containerType}
</div>
)}
</div>
{/* Technology Stack (medium/full detail only) */}
{data.detailLevel !== 'minimal' && data.technology.length > 0 && (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 4, marginBottom: 8 }}>
{data.technology.map((tech, idx) => (
<span
key={idx}
style={{
fontSize: 9,
padding: '2px 4px',
borderRadius: 3,
backgroundColor: '#e2e8f0',
color: '#334155',
}}
>
{tech}
</span>
))}
</div>
)}
{/* Description (full detail only) */}
{data.detailLevel === 'full' && (
<div
style={{
fontSize: 11,
color: '#64748b',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitLineClamp: 3,
WebkitBoxOrient: 'vertical',
}}
>
{data.description}
</div>
)}
</div>
);
});
ContainerNode.displayName = 'ContainerNode';ComponentNode ( import { memo } from 'react';
import { Handle, Position, NodeProps } from '@xyflow/react';
interface ComponentNodeData {
label: string;
description: string;
technology: string[];
c4Type: 'component';
detailLevel: 'minimal' | 'medium' | 'full';
architecturalRole?: string; // "Controller" | "Service" | "Repository"
changesetStatus?: 'new' | 'modified' | 'deleted';
}
export const ComponentNode = memo(({ data }: NodeProps<ComponentNodeData>) => {
const borderColor = data.changesetStatus === 'new' ? '#4ade80'
: data.changesetStatus === 'modified' ? '#fb923c'
: data.changesetStatus === 'deleted' ? '#ef4444'
: '#94a3b8'; // Default gray (nested appearance)
const borderStyle = data.changesetStatus === 'new' ? 'dashed' : 'solid';
const opacity = data.changesetStatus === 'deleted' ? 0.5 : 1;
return (
<div
style={{
width: 240, // MUST match precalculateDimensions
height: 140,
display: 'flex',
flexDirection: 'column',
border: `1.5px ${borderStyle} ${borderColor}`,
backgroundColor: '#f8fafc', // Lighter for nested appearance
borderRadius: 6,
padding: 10,
opacity,
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.08)',
}}
>
<Handle
type="target"
position={Position.Top}
id="top"
style={{ background: '#555' }}
/>
<Handle
type="source"
position={Position.Bottom}
id="bottom"
style={{ background: '#555' }}
/>
<Handle
type="target"
position={Position.Left}
id="left"
style={{ background: '#555' }}
/>
<Handle
type="source"
position={Position.Right}
id="right"
style={{ background: '#555' }}
/>
{/* Header: Name + Role Badge */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
<div style={{ fontWeight: 600, fontSize: data.detailLevel === 'minimal' ? 11 : 13, overflow: 'hidden', textOverflow: 'ellipsis' }}>
{data.label}
</div>
{data.detailLevel !== 'minimal' && data.architecturalRole && (
<div
style={{
fontSize: 9,
padding: '2px 4px',
borderRadius: 3,
backgroundColor: '#cbd5e1',
color: '#334155',
fontWeight: 500,
}}
>
{data.architecturalRole}
</div>
)}
</div>
{/* Technology Stack (medium/full only) */}
{data.detailLevel !== 'minimal' && data.technology.length > 0 && (
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 3, marginBottom: 6 }}>
{data.technology.slice(0, 3).map((tech, idx) => (
<span
key={idx}
style={{
fontSize: 8,
padding: '1px 3px',
borderRadius: 2,
backgroundColor: '#e2e8f0',
color: '#475569',
}}
>
{tech}
</span>
))}
</div>
)}
{/* Description (full only) */}
{data.detailLevel === 'full' && (
<div
style={{
fontSize: 10,
color: '#64748b',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
}}
>
{data.description}
</div>
)}
</div>
);
});
ComponentNode.displayName = 'ComponentNode';ExternalActorNode ( import { memo } from 'react';
import { Handle, Position, NodeProps } from '@xyflow/react';
interface ExternalActorNodeData {
label: string;
description: string;
c4Type: 'external';
detailLevel: 'minimal' | 'medium' | 'full';
}
export const ExternalActorNode = memo(({ data }: NodeProps<ExternalActorNodeData>) => {
return (
<div
style={{
width: 160, // MUST match precalculateDimensions
height: 120,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
border: '2px solid #64748b',
backgroundColor: '#f1f5f9',
borderRadius: 12,
padding: 8,
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.08)',
}}
>
<Handle
type="target"
position={Position.Top}
id="top"
style={{ background: '#555' }}
/>
<Handle
type="source"
position={Position.Bottom}
id="bottom"
style={{ background: '#555' }}
/>
<Handle
type="source"
position={Position.Right}
id="right"
style={{ background: '#555' }}
/>
<Handle
type="target"
position={Position.Left}
id="left"
style={{ background: '#555' }}
/>
{/* Icon */}
<div style={{ fontSize: 32, marginBottom: 8 }}>
👤
</div>
{/* Name */}
<div style={{ fontWeight: 600, fontSize: data.detailLevel === 'minimal' ? 11 : 12, textAlign: 'center', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{data.label}
</div>
{/* Description (medium/full only) */}
{data.detailLevel !== 'minimal' && (
<div
style={{
fontSize: 9,
color: '#64748b',
textAlign: 'center',
marginTop: 4,
overflow: 'hidden',
textOverflow: 'ellipsis',
}}
>
{data.description}
</div>
)}
</div>
);
});
ExternalActorNode.displayName = 'ExternalActorNode';Node Registration ( // Add imports
import { ContainerNode } from './c4/ContainerNode';
import { ComponentNode } from './c4/ComponentNode';
import { ExternalActorNode } from './c4/ExternalActorNode';
// Export components
export { ContainerNode, ComponentNode, ExternalActorNode };
// Add to nodeTypes object
export const nodeTypes = {
// ... existing nodes
c4Container: ContainerNode,
c4Component: ComponentNode,
c4ExternalActor: ExternalActorNode,
};Dimension Registration ( // In precalculateDimensions() method
case 'c4Container':
element.visual.size = { width: 280, height: 180 };
break;
case 'c4Component':
element.visual.size = { width: 240, height: 140 };
break;
case 'c4ExternalActor':
element.visual.size = { width: 160, height: 120 };
break;Acceptance Criteria:
Dependencies: None (can develop in parallel with Phase 2, integrate in Phase 4) Parent Issue: #7 Discussion: This work is detailed in discussion 8 Phase 4: C4GraphView Component & UI IntegrationTitle: Phase 4: C4GraphView Component - UI integration, navigation, and controls Description: Requirements:
Design Guidance: ViewPreferenceStore Extension ( interface C4Preferences {
viewLevel: 'context' | 'container' | 'component';
selectedContainerId?: string;
selectedComponentId?: string;
selectedLayout: 'hierarchical' | 'orthogonal' | 'force' | 'manual';
visibleContainerTypes: Set<string>;
visibleTechnologyStacks: Set<string>;
showDeploymentOverlay: boolean;
manualPositions: Map<string, { x: number; y: number }>; // Keyed by viewLevel + nodeId
focusContextEnabled: boolean;
pathTracing: {
mode: 'upstream' | 'downstream' | 'between' | 'none';
sourceId?: string;
targetId?: string;
};
scenarioPreset?: 'dataFlow' | 'deployment' | 'technologyStack' | 'apiSurface' | 'dependency' | null;
}
// Add to store
export const useViewPreferenceStore = create<ViewPreferenceState>()(
persist(
(set, get) => ({
// ... existing preferences
c4Preferences: {
viewLevel: 'context',
selectedLayout: 'hierarchical',
visibleContainerTypes: new Set(),
visibleTechnologyStacks: new Set(),
showDeploymentOverlay: false,
manualPositions: new Map(),
focusContextEnabled: false,
pathTracing: { mode: 'none' },
scenarioPreset: null,
},
setC4ViewLevel: (level) => set((state) => ({
c4Preferences: { ...state.c4Preferences, viewLevel: level }
})),
setC4SelectedContainer: (id) => set((state) => ({
c4Preferences: { ...state.c4Preferences, selectedContainerId: id }
})),
// ... additional setters
}),
{ name: 'dr-viewer-preferences' }
)
);C4GraphView Component ( import { useCallback, useEffect, useMemo, useState } from 'react';
import { ReactFlow, Background, Controls, MiniMap, useReactFlow } from '@xyflow/react';
import { useElementStore } from '../stores/elementStore';
import { useViewPreferenceStore } from '../stores/viewPreferenceStore';
import { parseC4Graph } from '../services/c4Parser';
import { transformC4GraphToReactFlow } from '../services/c4ViewTransformer';
import { nodeTypes } from '../../core/nodes';
import { C4BreadcrumbNav } from './C4BreadcrumbNav';
import { C4ViewLevelSwitcher } from './C4ViewLevelSwitcher';
import { C4FilterPanel } from './C4FilterPanel';
import { C4ControlPanel } from './C4ControlPanel';
import { C4InspectorPanel } from './C4InspectorPanel';
import { animateLayoutTransition } from '../services/layoutAnimations';
import type { C4Graph } from '../types/c4Graph';
import type { AppNode, AppEdge } from '../../core/types/reactflow';
export function C4GraphView() {
const { model } = useElementStore();
const { c4Preferences, setC4ViewLevel, setC4SelectedContainer, setC4ManualPositions } = useViewPreferenceStore();
const reactFlowInstance = useReactFlow();
const [c4Graph, setC4Graph] = useState<C4Graph | null>(null);
const [nodes, setNodes] = useState<AppNode[]>([]);
const [edges, setEdges] = useState<AppEdge[]>([]);
const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
// Step 1: Build C4Graph from model
useEffect(() => {
if (!model) return;
try {
const graph = parseC4Graph(model);
setC4Graph(graph);
} catch (error) {
console.error('Failed to parse C4 graph:', error);
}
}, [model]);
// Step 2: Transform C4Graph to ReactFlow nodes/edges
useEffect(() => {
if (!c4Graph) return;
const options = {
viewLevel: c4Preferences.viewLevel,
selectedContainerId: c4Preferences.selectedContainerId,
selectedComponentId: c4Preferences.selectedComponentId,
layoutAlgorithm: c4Preferences.selectedLayout,
filterOptions: {
containerTypes: c4Preferences.visibleContainerTypes,
technologyStack: c4Preferences.visibleTechnologyStacks,
showDeployment: c4Preferences.showDeploymentOverlay,
},
focusContext: {
enabled: c4Preferences.focusContextEnabled,
focusedNodeId: selectedNodeId,
},
pathHighlighting: c4Preferences.pathTracing,
semanticZoom: {
enabled: true,
currentScale: reactFlowInstance?.getViewport().zoom || 1,
},
};
const { nodes: newNodes, edges: newEdges } = transformC4GraphToReactFlow(c4Graph, options);
// Restore manual positions if applicable
if (c4Preferences.selectedLayout === 'manual') {
newNodes.forEach(node => {
const savedPos = c4Preferences.manualPositions.get(`${c4Preferences.viewLevel}-${node.id}`);
if (savedPos) {
node.position = savedPos;
}
});
}
// Animate transition
if (nodes.length > 0) {
animateLayoutTransition(nodes, newNodes, setNodes, setEdges, newEdges);
} else {
setNodes(newNodes);
setEdges(newEdges);
}
}, [c4Graph, c4Preferences, selectedNodeId, reactFlowInstance]);
// Handle drill-down navigation
const onNodeClick = useCallback((event: React.MouseEvent, node: AppNode) => {
setSelectedNodeId(node.id);
// Drill down based on current view level and node type
if (c4Preferences.viewLevel === 'context' && node.data.c4Type === 'container') {
// Drill down to container view
setC4SelectedContainer(node.id);
setC4ViewLevel('container');
} else if (c4Preferences.viewLevel === 'container' && node.data.c4Type === 'component') {
// Drill down to component view (if implemented)
// setC4SelectedComponent(node.id);
// setC4ViewLevel('component');
}
}, [c4Preferences.viewLevel, setC4ViewLevel, setC4SelectedContainer]);
// Handle manual positioning
const onNodeDragStop = useCallback(
(_event: any, _node: any) => {
if (c4Preferences.selectedLayout === 'manual') {
const positions = new Map<string, { x: number; y: number }>();
nodes.forEach((n) => {
positions.set(`${c4Preferences.viewLevel}-${n.id}`, { x: n.position.x, y: n.position.y });
});
setC4ManualPositions(positions);
}
},
[c4Preferences.selectedLayout, c4Preferences.viewLevel, nodes, setC4ManualPositions]
);
// Keyboard navigation
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setSelectedNodeId(null);
}
// Arrow key navigation: implement neighbor traversal
// Tab: cycle through nodes
// Ctrl/Cmd+F: open filter panel
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, []);
return (
<div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column' }}>
{/* Breadcrumb Navigation */}
<C4BreadcrumbNav />
{/* View Level Switcher */}
<C4ViewLevelSwitcher />
<div style={{ flex: 1, display: 'flex' }}>
{/* Filter Panel (left sidebar) */}
<C4FilterPanel c4Graph={c4Graph} />
{/* ReactFlow Canvas */}
<div style={{ flex: 1, position: 'relative' }}>
<ReactFlow
nodes={nodes}
edges={edges}
nodeTypes={nodeTypes}
onNodeClick={onNodeClick}
onNodeDragStop={onNodeDragStop}
fitView
>
<Background />
<Controls />
<MiniMap />
</ReactFlow>
{/* Control Panel (overlay) */}
<C4ControlPanel />
</div>
{/* Inspector Panel (right sidebar) */}
{selectedNodeId && <C4InspectorPanel nodeId={selectedNodeId} c4Graph={c4Graph} />}
</div>
</div>
);
}C4BreadcrumbNav Component ( import { useViewPreferenceStore } from '../stores/viewPreferenceStore';
export function C4BreadcrumbNav() {
const { c4Preferences, setC4ViewLevel, setC4SelectedContainer } = useViewPreferenceStore();
const segments = [
{ level: 'context', label: 'System Context', nodeId: null },
];
if (c4Preferences.viewLevel === 'container' || c4Preferences.viewLevel === 'component') {
segments.push({
level: 'container',
label: c4Preferences.selectedContainerId || 'Container',
nodeId: c4Preferences.selectedContainerId,
});
}
if (c4Preferences.viewLevel === 'component') {
segments.push({
level: 'component',
label: c4Preferences.selectedComponentId || 'Component',
nodeId: c4Preferences.selectedComponentId,
});
}
return (
<div style={{ padding: '8px 16px', borderBottom: '1px solid #e2e8f0', display: 'flex', gap: 8 }}>
{segments.map((seg, idx) => (
<div key={idx} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<button
onClick={() => {
setC4ViewLevel(seg.level as any);
if (seg.level === 'context') {
setC4SelectedContainer(undefined);
}
}}
style={{
background: 'none',
border: 'none',
color: idx === segments.length - 1 ? '#334155' : '#64748b',
fontWeight: idx === segments.length - 1 ? 600 : 400,
cursor: 'pointer',
}}
>
{seg.label}
</button>
{idx < segments.length - 1 && <span style={{ color: '#94a3b8' }}>›</span>}
</div>
))}
</div>
);
}Router Integration ( const c4Route = createRoute({
getParentRoute: () => rootRoute,
path: '/architecture',
component: () => <C4GraphView />,
});
export const routeTree = rootRoute.addChildren([
// ... existing routes
c4Route,
]);Navigation Integration ( Acceptance Criteria:
Dependencies: Phase 1 (C4Parser), Phase 2 (C4ViewTransformer), Phase 3 (Custom nodes) Parent Issue: #7 Discussion: This work is detailed in discussion 8 Phase 5: Advanced Features - Focus, Tracing, Presets, TestingTitle: Phase 5: Advanced Features - Focus+context, path tracing, scenario presets, comprehensive testing Description: Requirements:
Design Guidance: Focus+Context Implementation (extend C4ViewTransformer): private applyFocusContext(graph: C4Graph): C4Graph {
const { focusContext } = this.options;
if (!focusContext.enabled || !focusContext.focusedNodeId) {
return graph; // No focus applied
}
const focusedId = focusContext.focusedNodeId;
const immediateNeighbors = new Set<string>();
const distantNodes = new Set<string>();
// Find immediate neighbors (1 hop away)
graph.edges.forEach(edge => {
if (edge.sourceId === focusedId) {
immediateNeighbors.add(edge.targetId);
}
if (edge.targetId === focusedId) {
immediateNeighbors.add(edge.sourceId);
}
});
// All other nodes are distant
graph.nodes.forEach((node, id) => {
if (id !== focusedId && !immediateNeighbors.has(id)) {
distantNodes.add(id);
}
});
// Modify node data to indicate focus level
const focusedGraph = { ...graph };
focusedGraph.nodes = new Map(graph.nodes);
focusedGraph.nodes.forEach((node, id) => {
if (id === focusedId) {
node.data.focusLevel = 'full';
} else if (immediateNeighbors.has(id)) {
node.data.focusLevel = 'medium';
} else {
node.data.focusLevel = 'minimal';
node.data.opacity = 0.3; // Dim distant nodes
}
});
return focusedGraph;
}Path Tracing Implementation (C4PathTracer service): // src/apps/embedded/services/c4PathTracer.ts
export function traceUpstream(graph: C4Graph, targetId: string): Set<string> {
const upstream = new Set<string>();
const queue = [targetId];
const visited = new Set<string>();
while (queue.length > 0) {
const current = queue.shift()!;
if (visited.has(current)) continue;
visited.add(current);
graph.edges.forEach(edge => {
if (edge.targetId === current && !visited.has(edge.sourceId)) {
upstream.add(edge.sourceId);
queue.push(edge.sourceId);
}
});
}
return upstream;
}
export function traceDownstream(graph: C4Graph, sourceId: string): Set<string> {
const downstream = new Set<string>();
const queue = [sourceId];
const visited = new Set<string>();
while (queue.length > 0) {
const current = queue.shift()!;
if (visited.has(current)) continue;
visited.add(current);
graph.edges.forEach(edge => {
if (edge.sourceId === current && !visited.has(edge.targetId)) {
downstream.add(edge.targetId);
queue.push(edge.targetId);
}
});
}
return downstream;
}
export function findPathBetween(graph: C4Graph, sourceId: string, targetId: string): string[] | null {
// BFS to find shortest path
const queue = [[sourceId]];
const visited = new Set<string>();
while (queue.length > 0) {
const path = queue.shift()!;
const current = path[path.length - 1];
if (current === targetId) {
return path; // Found path
}
if (visited.has(current)) continue;
visited.add(current);
graph.edges.forEach(edge => {
if (edge.sourceId === current && !visited.has(edge.targetId)) {
queue.push([...path, edge.targetId]);
}
});
}
return null; // No path found
}Scenario Presets (C4ControlPanel): const scenarioPresets = {
dataFlow: {
name: 'Data Flow View',
filterOptions: {
// Emphasize data movement, hide control flow
edgeTypes: new Set(['sync', 'async']),
containerTypes: new Set(['database', 'messageQueue', 'cache']),
},
},
deployment: {
name: 'Deployment View',
filterOptions: {
showDeployment: true,
// Overlay container-to-infrastructure mappings
},
},
technologyStack: {
name: 'Technology Stack View',
groupBy: 'technology', // Group containers by technology instead of logical architecture
},
apiSurface: {
name: 'API Surface View',
filterOptions: {
// Highlight all external interfaces
containerTypes: new Set(['apiService', 'webApp']),
},
highlightExternalEdges: true,
},
dependency: {
name: 'Dependency View',
filterOptions: {
// Show only dependency relationships, hide data flow
edgeTypes: new Set(['dependency']),
},
},
};Changeset Visualization (already integrated in node components):
Testing Strategy: Unit Tests ( describe('C4Parser', () => {
test('detects containers from application layer services with external APIs', () => {
const model = createMockModel({
layers: {
application: [
{ id: 'app-1', type: 'service', relationships: [{ type: 'exposes', targetId: 'api-1' }] },
],
api: [
{ id: 'api-1', type: 'endpoint', properties: { method: 'GET', path: '/users' } },
],
},
});
const graph = parseC4Graph(model);
expect(graph.nodes.get('app-1')?.c4Type).toBe('container');
expect(graph.hierarchy.systemBoundary).toContain('app-1');
});
test('infers container relationships from API endpoints', () => {
// Test API endpoint → container edge creation
});
test('extracts technology stack from properties and relationships', () => {
// Test technology extraction logic
});
test('validates hierarchy has no cycles', () => {
// Test cycle detection
});
// ... 30+ test cases covering all mapping rules
});Integration Tests ( describe('C4GraphBuilder with example-implementation model', () => {
test('parses 182-element model successfully', async () => {
const model = await loadExampleImplementationModel();
const graph = parseC4Graph(model);
expect(graph.nodes.size).toBeGreaterThan(10); // At least 10 containers
expect(graph.edges.size).toBeGreaterThan(20); // At least 20 relationships
});
test('produces expected container count', () => {
// Manual analysis baseline: expect ~15 containers
});
test('validates hierarchy structure', () => {
// No cycles, valid references
});
test('parse time < 500ms', () => {
const start = performance.now();
parseC4Graph(model);
const duration = performance.now() - start;
expect(duration).toBeLessThan(500);
});
});E2E Tests ( import { test, expect } from '@playwright/test';
test.describe('C4 Visualization', () => {
test('loads model and displays System Context view', async ({ page }) => {
await page.goto('/architecture');
await expect(page.locator('text=System Context')).toBeVisible();
// Verify containers rendered
});
test('drill-down navigation: Context → Container → Component', async ({ page }) => {
// Click container in Context view
// Verify breadcrumb shows "Context › [Container]"
// Verify components displayed
});
test('breadcrumb navigation back to Context', async ({ page }) => {
// Navigate to Container view
// Click "System Context" breadcrumb
// Verify Context view displayed
});
test('filter panel updates diagram', async ({ page }) => {
// Open filter panel
// Check "database" container type
// Verify only database containers visible
});
test('export PNG downloads file', async ({ page }) => {
// Click Export → PNG
// Verify download initiated
});
test('layout switching animates smoothly', async ({ page }) => {
// Switch from hierarchical to force-directed
// Verify smooth transition (no flicker)
});
test('drill-down latency < 300ms', async ({ page }) => {
const start = Date.now();
await page.click('[data-testid="container-node-1"]');
await page.waitForSelector('[data-testid="component-node"]');
const duration = Date.now() - start;
expect(duration).toBeLessThan(300);
});
});Accessibility Tests ( import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('C4 view meets WCAG 2.1 AA standards', async ({ page }) => {
await page.goto('/architecture');
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
test('keyboard navigation works', async ({ page }) => {
await page.goto('/architecture');
// Tab to first container
await page.keyboard.press('Tab');
await expect(page.locator('[data-testid="container-node"]:focus')).toBeVisible();
// Escape to deselect
await page.keyboard.press('Escape');
await expect(page.locator('[data-testid="container-node"]:focus')).not.toBeVisible();
});Performance Tests ( test('initial render time < 3s for 50 containers', async ({ page }) => {
const start = Date.now();
await page.goto('/architecture');
await page.waitForSelector('[data-testid="container-node"]');
const duration = Date.now() - start;
expect(duration).toBeLessThan(3000);
});
test('filter operations < 500ms', async ({ page }) => {
await page.goto('/architecture');
const start = Date.now();
await page.check('[data-testid="filter-database"]');
await page.waitForTimeout(100); // Wait for debounce
const duration = Date.now() - start;
expect(duration).toBeLessThan(500);
});Acceptance Criteria:
Dependencies: Phase 4 (requires C4GraphView component) Parent Issue: #7 Discussion: This work is detailed in discussion 8 Dependencies Summary
Recommended Execution Order: Phase 1 → Phase 2 and Phase 3 (parallel) → Phase 4 → Phase 5 Next Steps
Generated by Orchestrator Bot 🤖 |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Requirements Analysis
Auto-created from Issue #7
User Request
Add the ability to visualize the application and technology layers as a set of C4 Model Architecture diagrams which richly communicate the details of the application and technology layers to help viewers understand the details of the complex data model.
Think hard about how to present a rich and flexible C4 Model Architecture diagrams including determining which controls are needed for an intuitive ability to control the level of detail displayed. Research best practices. Think about how the user will want to focus in on sub-sets of the C4 model to control how much complexity is on the screen, tracing inputs and outputs to build an accurate mental model by viewing the visualization. Think about which connections matter and how to present them well. Think about how a user can focus in on a single portion of the C4 model, while also seeing how it connects to the broader business ecosystem.
The visualization should utilize the existing reactflow foundation but with different layouts, nodes, and links from the current spec and model graph views.
The inputs to the system are:
The software design should be a parser which draws on the model and spec and creates a standard output which can be visualized by the C4 Model diagrams. Focus extensively on testing the parser to ensure that it is reliable, repeatable, and maintainable. It should execute 100% in the browser.
Labels: None
Requested by: @tinkermonkey
The orchestrator will analyze this request and develop detailed requirements.
When complete, Issue #7 will be updated with final requirements.
Beta Was this translation helpful? Give feedback.
All reactions