344 lines
14 KiB
Python
344 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Design visualization for guardrail workflow.
|
|
|
|
Generates ASCII art visualization of pages, components, and API endpoints
|
|
from the project manifest.
|
|
|
|
Usage:
|
|
python3 visualize_design.py --manifest project_manifest.json
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def load_manifest(manifest_path: str) -> dict | None:
|
|
"""Load manifest if it exists."""
|
|
if not os.path.exists(manifest_path):
|
|
return None
|
|
try:
|
|
with open(manifest_path) as f:
|
|
return json.load(f)
|
|
except (json.JSONDecodeError, IOError):
|
|
return None
|
|
|
|
|
|
def get_status_icon(status: str) -> str:
|
|
"""Get icon for entity status."""
|
|
icons = {
|
|
'PENDING': '⏳',
|
|
'APPROVED': '✅',
|
|
'IMPLEMENTED': '🟢',
|
|
'IN_PROGRESS': '🔄',
|
|
'REJECTED': '❌',
|
|
}
|
|
return icons.get(status, '○')
|
|
|
|
|
|
def visualize_page(page: dict, components: list, indent: str = "") -> list:
|
|
"""Generate ASCII visualization for a page."""
|
|
lines = []
|
|
name = page.get('name', 'Unknown')
|
|
status = page.get('status', 'PENDING')
|
|
file_path = page.get('file_path', '')
|
|
description = page.get('description', '')
|
|
|
|
icon = get_status_icon(status)
|
|
|
|
# Page header
|
|
lines.append(f"{indent}┌{'─' * 60}┐")
|
|
lines.append(f"{indent}│ {icon} PAGE: {name:<50} │")
|
|
lines.append(f"{indent}│ {' ' * 3}Path: {file_path:<48} │")
|
|
if description:
|
|
desc_short = description[:45] + '...' if len(description) > 45 else description
|
|
lines.append(f"{indent}│ {' ' * 3}Desc: {desc_short:<48} │")
|
|
lines.append(f"{indent}├{'─' * 60}┤")
|
|
|
|
# Find components used by this page
|
|
page_components = []
|
|
page_id = page.get('id', '')
|
|
for comp in components:
|
|
deps = comp.get('dependencies', [])
|
|
used_by = comp.get('used_by', [])
|
|
if page_id in deps or page_id in used_by or page.get('name', '').lower() in str(comp).lower():
|
|
page_components.append(comp)
|
|
|
|
if page_components:
|
|
lines.append(f"{indent}│ COMPONENTS: │")
|
|
for comp in page_components:
|
|
comp_name = comp.get('name', 'Unknown')
|
|
comp_status = comp.get('status', 'PENDING')
|
|
comp_icon = get_status_icon(comp_status)
|
|
lines.append(f"{indent}│ {comp_icon} {comp_name:<53} │")
|
|
else:
|
|
lines.append(f"{indent}│ (No components defined yet) │")
|
|
|
|
lines.append(f"{indent}└{'─' * 60}┘")
|
|
|
|
return lines
|
|
|
|
|
|
def visualize_component_tree(components: list) -> list:
|
|
"""Generate ASCII tree of components."""
|
|
lines = []
|
|
|
|
if not components:
|
|
return [" (No components defined)"]
|
|
|
|
lines.append("┌─────────────────────────────────────────────────────────────┐")
|
|
lines.append("│ 🧩 COMPONENTS │")
|
|
lines.append("├─────────────────────────────────────────────────────────────┤")
|
|
|
|
for i, comp in enumerate(components):
|
|
name = comp.get('name', 'Unknown')
|
|
status = comp.get('status', 'PENDING')
|
|
file_path = comp.get('file_path', '')
|
|
icon = get_status_icon(status)
|
|
|
|
is_last = i == len(components) - 1
|
|
prefix = "└──" if is_last else "├──"
|
|
|
|
lines.append(f"│ {prefix} {icon} {name:<50} │")
|
|
lines.append(f"│ {' ' if is_last else '│ '} {file_path:<50} │")
|
|
|
|
lines.append("└─────────────────────────────────────────────────────────────┘")
|
|
|
|
return lines
|
|
|
|
|
|
def visualize_api_endpoints(endpoints: list) -> list:
|
|
"""Generate ASCII visualization of API endpoints."""
|
|
lines = []
|
|
|
|
if not endpoints:
|
|
return []
|
|
|
|
lines.append("┌─────────────────────────────────────────────────────────────┐")
|
|
lines.append("│ 🔌 API ENDPOINTS │")
|
|
lines.append("├─────────────────────────────────────────────────────────────┤")
|
|
|
|
for endpoint in endpoints:
|
|
name = endpoint.get('name', 'Unknown')
|
|
method = endpoint.get('method', 'GET')
|
|
path = endpoint.get('path', endpoint.get('file_path', ''))
|
|
status = endpoint.get('status', 'PENDING')
|
|
icon = get_status_icon(status)
|
|
|
|
method_colors = {
|
|
'GET': '🟢',
|
|
'POST': '🟡',
|
|
'PUT': '🟠',
|
|
'PATCH': '🟠',
|
|
'DELETE': '🔴',
|
|
}
|
|
method_icon = method_colors.get(method.upper(), '⚪')
|
|
|
|
lines.append(f"│ {icon} {method_icon} {method.upper():<6} {name:<45} │")
|
|
lines.append(f"│ Path: {path:<47} │")
|
|
|
|
lines.append("└─────────────────────────────────────────────────────────────┘")
|
|
|
|
return lines
|
|
|
|
|
|
def visualize_page_flow(pages: list) -> list:
|
|
"""Generate ASCII flow diagram of pages."""
|
|
lines = []
|
|
|
|
if not pages:
|
|
return []
|
|
|
|
lines.append("")
|
|
lines.append("📱 PAGE FLOW DIAGRAM")
|
|
lines.append("═" * 65)
|
|
lines.append("")
|
|
|
|
# Simple flow visualization
|
|
for i, page in enumerate(pages):
|
|
name = page.get('name', 'Unknown')
|
|
status = page.get('status', 'PENDING')
|
|
icon = get_status_icon(status)
|
|
|
|
# Page box
|
|
box_width = max(len(name) + 4, 20)
|
|
lines.append(f" ┌{'─' * box_width}┐")
|
|
lines.append(f" │ {icon} {name.center(box_width - 4)} │")
|
|
lines.append(f" └{'─' * box_width}┘")
|
|
|
|
# Arrow to next page (if not last)
|
|
if i < len(pages) - 1:
|
|
lines.append(f" {'│'.center(box_width + 4)}")
|
|
lines.append(f" {'▼'.center(box_width + 4)}")
|
|
lines.append("")
|
|
|
|
return lines
|
|
|
|
|
|
def visualize_data_flow(manifest: dict) -> list:
|
|
"""Generate data flow visualization."""
|
|
lines = []
|
|
|
|
pages = manifest.get('entities', {}).get('pages', [])
|
|
components = manifest.get('entities', {}).get('components', [])
|
|
endpoints = manifest.get('entities', {}).get('api_endpoints', [])
|
|
|
|
if not any([pages, components, endpoints]):
|
|
return []
|
|
|
|
lines.append("")
|
|
lines.append("🔄 DATA FLOW ARCHITECTURE")
|
|
lines.append("═" * 65)
|
|
lines.append("")
|
|
lines.append(" ┌─────────────────────────────────────────────────────────┐")
|
|
lines.append(" │ FRONTEND │")
|
|
lines.append(" │ ┌─────────┐ ┌─────────────┐ ┌───────────────┐ │")
|
|
lines.append(" │ │ Pages │───▶│ Components │───▶│ Hooks │ │")
|
|
|
|
page_count = len(pages)
|
|
comp_count = len(components)
|
|
lines.append(f" │ │ ({page_count:^3}) │ │ ({comp_count:^3}) │ │ (state) │ │")
|
|
lines.append(" │ └─────────┘ └─────────────┘ └───────┬───────┘ │")
|
|
lines.append(" │ │ │")
|
|
lines.append(" └────────────────────────────────────────────┼───────────┘")
|
|
lines.append(" │")
|
|
lines.append(" ▼")
|
|
lines.append(" ┌─────────────────────────────────────────────────────────┐")
|
|
lines.append(" │ BACKEND │")
|
|
lines.append(" │ ┌─────────────────────────────────────────────────┐ │")
|
|
lines.append(" │ │ API Endpoints │ │")
|
|
|
|
api_count = len(endpoints)
|
|
lines.append(f" │ │ ({api_count:^3}) │ │")
|
|
lines.append(" │ └──────────────────────────┬──────────────────────┘ │")
|
|
lines.append(" │ │ │")
|
|
lines.append(" │ ▼ │")
|
|
lines.append(" │ ┌─────────────────────────────────────────────────┐ │")
|
|
lines.append(" │ │ Database │ │")
|
|
lines.append(" │ └─────────────────────────────────────────────────┘ │")
|
|
lines.append(" └─────────────────────────────────────────────────────────┘")
|
|
|
|
return lines
|
|
|
|
|
|
def generate_full_visualization(manifest: dict) -> str:
|
|
"""Generate complete design visualization."""
|
|
lines = []
|
|
|
|
project_name = manifest.get('project', {}).get('name', 'Unknown Project')
|
|
entities = manifest.get('entities', {})
|
|
|
|
pages = entities.get('pages', [])
|
|
components = entities.get('components', [])
|
|
api_endpoints = entities.get('api_endpoints', [])
|
|
|
|
# Header
|
|
lines.append("")
|
|
lines.append("╔═══════════════════════════════════════════════════════════════╗")
|
|
lines.append("║ 📐 DESIGN VISUALIZATION ║")
|
|
lines.append(f"║ Project: {project_name:<51} ║")
|
|
lines.append("╚═══════════════════════════════════════════════════════════════╝")
|
|
lines.append("")
|
|
|
|
# Summary counts
|
|
lines.append("📊 ENTITY SUMMARY")
|
|
lines.append("─" * 65)
|
|
lines.append(f" Pages: {len(pages):>3} │ Components: {len(components):>3} │ API Endpoints: {len(api_endpoints):>3}")
|
|
lines.append("")
|
|
|
|
# Page Flow
|
|
if pages:
|
|
lines.extend(visualize_page_flow(pages))
|
|
lines.append("")
|
|
|
|
# Detailed Pages with Components
|
|
if pages:
|
|
lines.append("")
|
|
lines.append("📄 PAGE DETAILS")
|
|
lines.append("═" * 65)
|
|
for page in pages:
|
|
lines.extend(visualize_page(page, components))
|
|
lines.append("")
|
|
|
|
# Component Tree
|
|
if components:
|
|
lines.append("")
|
|
lines.append("🧩 COMPONENT HIERARCHY")
|
|
lines.append("═" * 65)
|
|
lines.extend(visualize_component_tree(components))
|
|
lines.append("")
|
|
|
|
# API Endpoints
|
|
if api_endpoints:
|
|
lines.append("")
|
|
lines.append("🔌 API LAYER")
|
|
lines.append("═" * 65)
|
|
lines.extend(visualize_api_endpoints(api_endpoints))
|
|
lines.append("")
|
|
|
|
# Data Flow Architecture
|
|
lines.extend(visualize_data_flow(manifest))
|
|
lines.append("")
|
|
|
|
# Legend
|
|
lines.append("")
|
|
lines.append("📋 LEGEND")
|
|
lines.append("─" * 65)
|
|
lines.append(" ⏳ PENDING - Designed, awaiting approval")
|
|
lines.append(" ✅ APPROVED - Approved, ready for implementation")
|
|
lines.append(" 🔄 IN_PROGRESS - Currently being implemented")
|
|
lines.append(" 🟢 IMPLEMENTED - Implementation complete")
|
|
lines.append("")
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Visualize design from manifest")
|
|
parser.add_argument("--manifest", required=True, help="Path to project_manifest.json")
|
|
parser.add_argument("--format", choices=['full', 'pages', 'components', 'api', 'flow'],
|
|
default='full', help="Visualization format")
|
|
args = parser.parse_args()
|
|
|
|
manifest = load_manifest(args.manifest)
|
|
|
|
if manifest is None:
|
|
print("❌ Error: Could not load manifest from", args.manifest)
|
|
return 1
|
|
|
|
entities = manifest.get('entities', {})
|
|
|
|
if not any([
|
|
entities.get('pages'),
|
|
entities.get('components'),
|
|
entities.get('api_endpoints')
|
|
]):
|
|
print("⚠️ No entities found in manifest. Design phase may not be complete.")
|
|
return 0
|
|
|
|
if args.format == 'full':
|
|
print(generate_full_visualization(manifest))
|
|
elif args.format == 'pages':
|
|
pages = entities.get('pages', [])
|
|
components = entities.get('components', [])
|
|
for page in pages:
|
|
print("\n".join(visualize_page(page, components)))
|
|
elif args.format == 'components':
|
|
components = entities.get('components', [])
|
|
print("\n".join(visualize_component_tree(components)))
|
|
elif args.format == 'api':
|
|
endpoints = entities.get('api_endpoints', [])
|
|
print("\n".join(visualize_api_endpoints(endpoints)))
|
|
elif args.format == 'flow':
|
|
pages = entities.get('pages', [])
|
|
print("\n".join(visualize_page_flow(pages)))
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|