Skip to content

Commit 3b512ee

Browse files
authored
Merge pull request #115 from codervisor/copilot/implement-spec-193
feat(ui-vite): Implement core visualization features for UI parity (Spec 193, Phase 1-2)
2 parents 0fe9697 + d15c287 commit 3b512ee

File tree

11 files changed

+1544
-231
lines changed

11 files changed

+1544
-231
lines changed

packages/ui-vite/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,19 @@
1414
"test:watch": "vitest"
1515
},
1616
"dependencies": {
17+
"@dagrejs/dagre": "^1.1.8",
1718
"@leanspec/ui-components": "workspace:*",
1819
"clsx": "^2.1.1",
20+
"cmdk": "^1.1.1",
1921
"lucide-react": "^0.553.0",
22+
"mermaid": "^11.12.2",
2023
"react": "^19.2.3",
2124
"react-dom": "^19.2.3",
2225
"react-markdown": "^9.1.0",
2326
"react-router-dom": "^7.11.0",
27+
"react-syntax-highlighter": "^16.1.0",
28+
"reactflow": "^11.11.4",
29+
"recharts": "^3.6.0",
2430
"rehype-raw": "^7.0.0",
2531
"remark-gfm": "^4.0.1",
2632
"tailwind-merge": "^3.4.0"
@@ -35,6 +41,7 @@
3541
"@types/node": "^24.10.4",
3642
"@types/react": "^19.2.7",
3743
"@types/react-dom": "^19.2.3",
44+
"@types/react-syntax-highlighter": "^15.5.13",
3845
"@vitejs/plugin-react": "^5.1.2",
3946
"autoprefixer": "^10.4.23",
4047
"eslint": "^9.39.2",

packages/ui-vite/src/components/Layout.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Outlet, Link, useLocation } from 'react-router-dom';
2-
import { BarChart3, FileText, Network, Settings, Keyboard } from 'lucide-react';
2+
import { BarChart3, FileText, Network, Settings, Keyboard, LayoutDashboard } from 'lucide-react';
33
import { cn } from '../lib/utils';
44
import { ProjectSwitcher } from './ProjectSwitcher';
55
import { ThemeToggle } from './ThemeToggle';
@@ -8,6 +8,7 @@ import { useState } from 'react';
88

99
function KeyboardShortcutsHelp({ onClose }: { onClose: () => void }) {
1010
const shortcuts = [
11+
{ key: 'h', description: 'Go to dashboard (home)' },
1112
{ key: 'g', description: 'Go to specs list' },
1213
{ key: 's', description: 'Go to stats' },
1314
{ key: 'd', description: 'Go to dependencies' },
@@ -47,6 +48,7 @@ export function Layout() {
4748
useGlobalShortcuts();
4849

4950
const navItems = [
51+
{ path: '/', label: 'Dashboard', icon: LayoutDashboard },
5052
{ path: '/specs', label: 'Specs', icon: FileText },
5153
{ path: '/stats', label: 'Stats', icon: BarChart3 },
5254
{ path: '/dependencies', label: 'Dependencies', icon: Network },
@@ -63,7 +65,9 @@ export function Layout() {
6365
<nav className="flex gap-1">
6466
{navItems.map((item) => {
6567
const Icon = item.icon;
66-
const isActive = location.pathname.startsWith(item.path);
68+
const isActive = item.path === '/'
69+
? location.pathname === '/'
70+
: location.pathname.startsWith(item.path);
6771
return (
6872
<Link
6973
key={item.path}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
import mermaid from 'mermaid';
3+
4+
interface MermaidDiagramProps {
5+
chart: string;
6+
className?: string;
7+
}
8+
9+
// Initialize mermaid with configuration
10+
mermaid.initialize({
11+
startOnLoad: false,
12+
theme: 'default',
13+
securityLevel: 'loose',
14+
fontFamily: 'system-ui, -apple-system, sans-serif',
15+
});
16+
17+
// Generate unique IDs for Mermaid diagrams using crypto.randomUUID or fallback
18+
const generateId = () => {
19+
if (typeof crypto !== 'undefined' && crypto.randomUUID) {
20+
return `mermaid-${crypto.randomUUID()}`;
21+
}
22+
// Fallback for older browsers
23+
return `mermaid-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
24+
};
25+
26+
export function MermaidDiagram({ chart, className = '' }: MermaidDiagramProps) {
27+
const ref = useRef<HTMLDivElement>(null);
28+
const [svg, setSvg] = useState<string>('');
29+
const [error, setError] = useState<string | null>(null);
30+
const [id] = useState(generateId);
31+
32+
useEffect(() => {
33+
if (!chart || !ref.current) return;
34+
35+
const renderDiagram = async () => {
36+
try {
37+
setError(null);
38+
// Update theme based on document theme
39+
const isDark = document.documentElement.classList.contains('dark');
40+
mermaid.initialize({
41+
theme: isDark ? 'dark' : 'default',
42+
securityLevel: 'loose',
43+
});
44+
45+
const { svg } = await mermaid.render(id, chart);
46+
setSvg(svg);
47+
} catch (err) {
48+
console.error('Mermaid rendering error:', err);
49+
setError(err instanceof Error ? err.message : 'Failed to render diagram');
50+
}
51+
};
52+
53+
renderDiagram();
54+
}, [chart, id]);
55+
56+
if (error) {
57+
return (
58+
<div className={`border border-destructive rounded-lg p-4 ${className}`}>
59+
<div className="text-sm text-destructive">
60+
<strong>Mermaid Diagram Error:</strong>
61+
<pre className="mt-2 text-xs overflow-auto">{error}</pre>
62+
</div>
63+
</div>
64+
);
65+
}
66+
67+
if (!svg) {
68+
return (
69+
<div className={`border rounded-lg p-4 ${className}`}>
70+
<div className="text-sm text-muted-foreground">Loading diagram...</div>
71+
</div>
72+
);
73+
}
74+
75+
return (
76+
<div
77+
ref={ref}
78+
className={`mermaid-diagram border rounded-lg p-4 overflow-auto ${className}`}
79+
dangerouslySetInnerHTML={{ __html: svg }}
80+
/>
81+
);
82+
}

packages/ui-vite/src/hooks/useKeyboardShortcuts.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ export function useGlobalShortcuts() {
4444
const navigate = useNavigate();
4545

4646
const shortcuts: KeyboardShortcut[] = [
47+
{
48+
key: 'h',
49+
description: 'Go to dashboard (home)',
50+
action: useCallback(() => navigate('/'), [navigate]),
51+
},
4752
{
4853
key: 'g',
4954
description: 'Go to specs list',

0 commit comments

Comments
 (0)