From dd3f7345df81d86aeb6f52f9165f7998d0d49631 Mon Sep 17 00:00:00 2001 From: Spencer Francisco Date: Sun, 2 Nov 2025 08:36:48 -0600 Subject: [PATCH 1/2] fix: Refine diagram naming and display with tabs - Update extractMermaidDiagrams to assign descriptive names based on diagram type - Diagrams now named as user-flow, admin-flow, system-flow, etc. - Add Documents/Diagrams tabs on workflow complete screen - Documents tab shows requirements, design, and tasks - Diagrams tab displays all Mermaid diagrams with proper naming - Export ZIP now includes properly named diagram files Fixes #29 --- app/page.tsx | 163 ++++++++++++++++++++++++++++++++++++++++++++- lib/exportUtils.ts | 63 ++++++++++++++---- 2 files changed, 209 insertions(+), 17 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 63df21c..e42c967 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -17,6 +17,59 @@ import { useSimpleApiKeyStorage } from '@/hooks/useSimpleApiKeyStorage' import { useModelStorage, usePromptStorage, useContextFilesStorage } from '@/hooks/useSessionStorage' import { DotPattern } from '@/components/magicui/dot-pattern' +// Helper function to extract Mermaid diagrams with proper naming +function extractMermaidDiagrams(content: string): { name: string; content: string }[] { + const mermaidRegex = /```mermaid\n([\s\S]*?)\n```/g + const diagrams: { name: string; content: string }[] = [] + let match + + while ((match = mermaidRegex.exec(content)) !== null) { + const diagramContent = match[1].trim() + let diagramName = 'diagram' + + // Determine diagram type based on content + if (diagramContent.includes('graph') || diagramContent.includes('flowchart')) { + if (diagramContent.includes('user') || diagramContent.includes('User')) { + diagramName = 'user-flow' + } else if (diagramContent.includes('admin') || diagramContent.includes('Admin')) { + diagramName = 'admin-flow' + } else if (diagramContent.includes('system') || diagramContent.includes('System')) { + diagramName = 'system-flow' + } else if (diagramContent.includes('data') || diagramContent.includes('Data')) { + diagramName = 'data-flow' + } else if (diagramContent.includes('sequence') || diagramContent.includes('Sequence')) { + diagramName = 'sequence-diagram' + } else if (diagramContent.includes('class') || diagramContent.includes('Class')) { + diagramName = 'class-diagram' + } else if (diagramContent.includes('component') || diagramContent.includes('Component')) { + diagramName = 'component-diagram' + } else { + diagramName = 'flowchart' + } + } else if (diagramContent.includes('erDiagram') || diagramContent.includes('ER')) { + diagramName = 'entity-relationship' + } else if (diagramContent.includes('gantt') || diagramContent.includes('Gantt')) { + diagramName = 'gantt-chart' + } else if (diagramContent.includes('journey') || diagramContent.includes('Journey')) { + diagramName = 'user-journey' + } else if (diagramContent.includes('pie') || diagramContent.includes('Pie')) { + diagramName = 'pie-chart' + } else if (diagramContent.includes('git') || diagramContent.includes('Git')) { + diagramName = 'git-graph' + } + + // If we have multiple diagrams of the same type, add a suffix + const existingCount = diagrams.filter(d => d.name.startsWith(diagramName)).length + if (existingCount > 0) { + diagramName = `${diagramName}-${existingCount + 1}` + } + + diagrams.push({ name: diagramName, content: diagramContent }) + } + + return diagrams +} + export default function Home() { const { value: apiKey, hasValidKey, setAPIKey, clearAPIKey } = useSimpleApiKeyStorage() @@ -40,6 +93,7 @@ export default function Home() { const [justDidReset, setJustDidReset] = useState(false) // Track if we just did a reset const [expandedSpecs, setExpandedSpecs] = useState<{[key: string]: boolean}>({}) // Track expanded specifications const [showNewProjectNotification, setShowNewProjectNotification] = useState(false) // Track new project started + const [activeTab, setActiveTab] = useState<'documents' | 'diagrams'>('documents') // Track active tab in results const hasApiKey = Boolean(apiKey && hasValidKey) const hasModel = Boolean(selectedModel) @@ -1114,9 +1168,40 @@ export default function Home() { {/* Generated Content Previews */}
-

Generated Specifications

+
+

Generated Specifications

+ + {/* Tab Toggle */} +
+ + +
+
- {(['requirements', 'design', 'tasks'] as const).map((phase) => { + {/* Documents Tab */} + {activeTab === 'documents' && ( + <> + {(['requirements', 'design', 'tasks'] as const).map((phase) => { const content = workflow.state[phase] if (!content) return null @@ -1156,7 +1241,79 @@ export default function Home() { ) - })} + })} + + )} + + {/* Diagrams Tab */} + {activeTab === 'diagrams' && ( + <> + {(() => { + // Extract diagrams from all phases + const allDiagrams: { name: string; content: string; source: string }[] = [] + + // Check each phase for diagrams + if (workflow.state.requirements) { + const reqDiagrams = extractMermaidDiagrams(workflow.state.requirements) + reqDiagrams.forEach(d => allDiagrams.push({ ...d, source: 'requirements' })) + } + + if (workflow.state.design) { + const designDiagrams = extractMermaidDiagrams(workflow.state.design) + designDiagrams.forEach(d => allDiagrams.push({ ...d, source: 'design' })) + } + + if (workflow.state.tasks) { + const taskDiagrams = extractMermaidDiagrams(workflow.state.tasks) + taskDiagrams.forEach(d => allDiagrams.push({ ...d, source: 'tasks' })) + } + + if (allDiagrams.length === 0) { + return ( +
+ +
No Diagrams Generated
+
+ No Mermaid diagrams were found in the generated specifications. +
+
+ ) + } + + return allDiagrams.map((diagram, index) => { + const isExpanded = expandedSpecs[`diagram-${index}`] || false + + return ( + + + +
+ {diagram.name.replace(/-/g, ' ')} + + from {diagram.source} + +
+ +
+
+ +
+
{diagram.content}
+
+
+
+ ) + }) + })()} + + )}
{/* Debug: Add dummy data for testing */} diff --git a/lib/exportUtils.ts b/lib/exportUtils.ts index 5d7fb2e..541f332 100644 --- a/lib/exportUtils.ts +++ b/lib/exportUtils.ts @@ -61,15 +61,55 @@ function extractProjectNameFromTasks(tasks: string, fallbackFeatureName: string) } /** - * Extract Mermaid diagram code blocks from markdown content + * Extract Mermaid diagram code blocks from markdown content with proper naming */ -function extractMermaidDiagrams(content: string): string[] { +function extractMermaidDiagrams(content: string): { name: string; content: string }[] { const mermaidRegex = /```mermaid\n([\s\S]*?)\n```/g - const diagrams: string[] = [] + const diagrams: { name: string; content: string }[] = [] let match while ((match = mermaidRegex.exec(content)) !== null) { - diagrams.push(match[1].trim()) + const diagramContent = match[1].trim() + let diagramName = 'diagram' + + // Determine diagram type based on content + if (diagramContent.includes('graph') || diagramContent.includes('flowchart')) { + if (diagramContent.includes('user') || diagramContent.includes('User')) { + diagramName = 'user-flow' + } else if (diagramContent.includes('admin') || diagramContent.includes('Admin')) { + diagramName = 'admin-flow' + } else if (diagramContent.includes('system') || diagramContent.includes('System')) { + diagramName = 'system-flow' + } else if (diagramContent.includes('data') || diagramContent.includes('Data')) { + diagramName = 'data-flow' + } else if (diagramContent.includes('sequence') || diagramContent.includes('Sequence')) { + diagramName = 'sequence-diagram' + } else if (diagramContent.includes('class') || diagramContent.includes('Class')) { + diagramName = 'class-diagram' + } else if (diagramContent.includes('component') || diagramContent.includes('Component')) { + diagramName = 'component-diagram' + } else { + diagramName = 'flowchart' + } + } else if (diagramContent.includes('erDiagram') || diagramContent.includes('ER')) { + diagramName = 'entity-relationship' + } else if (diagramContent.includes('gantt') || diagramContent.includes('Gantt')) { + diagramName = 'gantt-chart' + } else if (diagramContent.includes('journey') || diagramContent.includes('Journey')) { + diagramName = 'user-journey' + } else if (diagramContent.includes('pie') || diagramContent.includes('Pie')) { + diagramName = 'pie-chart' + } else if (diagramContent.includes('git') || diagramContent.includes('Git')) { + diagramName = 'git-graph' + } + + // If we have multiple diagrams of the same type, add a suffix + const existingCount = diagrams.filter(d => d.name.startsWith(diagramName)).length + if (existingCount > 0) { + diagramName = `${diagramName}-${existingCount + 1}` + } + + diagrams.push({ name: diagramName, content: diagramContent }) } return diagrams @@ -89,17 +129,12 @@ export async function createSpecificationZip(data: SpecificationData): Promise 0) { - // If multiple diagrams, create numbered files - if (mermaidDiagrams.length === 1) { - zip.file('architecture.mermaid', mermaidDiagrams[0]) - } else { - mermaidDiagrams.forEach((diagram, index) => { - zip.file(`diagram-${index + 1}.mermaid`, diagram) - }) - } + mermaidDiagrams.forEach((diagram) => { + zip.file(`${diagram.name}.mermaid`, diagram.content) + }) } } @@ -138,7 +173,7 @@ Generated by OpenSpec on ${new Date().toLocaleDateString()} - **requirements.md** - Functional requirements and user stories - **design.md** - Technical design and architecture - **tasks.md** - Implementation tasks breakdown -- **architecture.mermaid** - System architecture diagrams (if generated) +- **{diagram-name}.mermaid** - Diagram files with descriptive names (e.g., user-flow.mermaid, system-architecture.mermaid) - **metadata.json** - Generation metadata and statistics ## Usage From e3649e14849e0265f0e69a87ef577793b0193005 Mon Sep 17 00:00:00 2001 From: Spencer Francisco Date: Sun, 2 Nov 2025 19:40:06 -0600 Subject: [PATCH 2/2] chore: Fix linting issues in page.tsx - Remove unused import CardDescription - Remove unused variable setAPIKey --- app/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index e42c967..92aa59e 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -7,7 +7,7 @@ import PromptInput from '@/components/PromptInput' import { ComponentErrorBoundary } from '@/components/ErrorBoundary' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { AlertCircle, CheckCircle, FileText, Layers, List, MessageSquare, Play, Loader2, Download, Key, Brain, Check, X } from 'lucide-react' import { useSpecWorkflow } from '@/hooks/useSpecWorkflow' @@ -71,7 +71,7 @@ function extractMermaidDiagrams(content: string): { name: string; content: strin } export default function Home() { - const { value: apiKey, hasValidKey, setAPIKey, clearAPIKey } = useSimpleApiKeyStorage() + const { value: apiKey, hasValidKey, clearAPIKey } = useSimpleApiKeyStorage() const { selectedModel, setModel, clearModel } = useModelStorage() const { prompt, setPrompt, clearPrompt } = usePromptStorage()