@jxrstudios/jxr 1.0.10 → 1.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/bin/jxr.js +6 -0
  2. package/dist/index.js +57 -2
  3. package/dist/jxr-server-manager.d.ts.map +1 -1
  4. package/package.json +1 -1
  5. package/src/jxr-server-manager.ts +65 -2
  6. package/zzz_react_template/App.tsx +43 -156
  7. package/zzz_react_template/components/ErrorBoundary.tsx +62 -0
  8. package/zzz_react_template/components/ManusDialog.tsx +85 -0
  9. package/zzz_react_template/components/Map.tsx +155 -0
  10. package/zzz_react_template/components/jxr/CodeEditor.tsx +313 -0
  11. package/zzz_react_template/components/jxr/FileExplorer.tsx +230 -0
  12. package/zzz_react_template/components/jxr/IDEShell.tsx +159 -0
  13. package/zzz_react_template/components/jxr/LandingPage.tsx +414 -0
  14. package/zzz_react_template/components/jxr/LivePreview.tsx +169 -0
  15. package/zzz_react_template/components/jxr/PerformanceDashboard.tsx +379 -0
  16. package/zzz_react_template/components/jxr/TopBar.tsx +149 -0
  17. package/zzz_react_template/components/ui/accordion.tsx +64 -0
  18. package/zzz_react_template/components/ui/alert-dialog.tsx +155 -0
  19. package/zzz_react_template/components/ui/alert.tsx +66 -0
  20. package/zzz_react_template/components/ui/aspect-ratio.tsx +9 -0
  21. package/zzz_react_template/components/ui/avatar.tsx +51 -0
  22. package/zzz_react_template/components/ui/badge.tsx +46 -0
  23. package/zzz_react_template/components/ui/breadcrumb.tsx +109 -0
  24. package/zzz_react_template/components/ui/button-group.tsx +83 -0
  25. package/zzz_react_template/components/ui/button.tsx +60 -0
  26. package/zzz_react_template/components/ui/calendar.tsx +211 -0
  27. package/zzz_react_template/components/ui/card.tsx +92 -0
  28. package/zzz_react_template/components/ui/carousel.tsx +239 -0
  29. package/zzz_react_template/components/ui/chart.tsx +355 -0
  30. package/zzz_react_template/components/ui/checkbox.tsx +30 -0
  31. package/zzz_react_template/components/ui/collapsible.tsx +31 -0
  32. package/zzz_react_template/components/ui/command.tsx +184 -0
  33. package/zzz_react_template/components/ui/context-menu.tsx +250 -0
  34. package/zzz_react_template/components/ui/dialog.tsx +209 -0
  35. package/zzz_react_template/components/ui/drawer.tsx +133 -0
  36. package/zzz_react_template/components/ui/dropdown-menu.tsx +255 -0
  37. package/zzz_react_template/components/ui/empty.tsx +104 -0
  38. package/zzz_react_template/components/ui/field.tsx +242 -0
  39. package/zzz_react_template/components/ui/form.tsx +168 -0
  40. package/zzz_react_template/components/ui/hover-card.tsx +42 -0
  41. package/zzz_react_template/components/ui/input-group.tsx +168 -0
  42. package/zzz_react_template/components/ui/input-otp.tsx +75 -0
  43. package/zzz_react_template/components/ui/input.tsx +70 -0
  44. package/zzz_react_template/components/ui/item.tsx +193 -0
  45. package/zzz_react_template/components/ui/kbd.tsx +28 -0
  46. package/zzz_react_template/components/ui/label.tsx +22 -0
  47. package/zzz_react_template/components/ui/menubar.tsx +274 -0
  48. package/zzz_react_template/components/ui/navigation-menu.tsx +168 -0
  49. package/zzz_react_template/components/ui/pagination.tsx +127 -0
  50. package/zzz_react_template/components/ui/popover.tsx +46 -0
  51. package/zzz_react_template/components/ui/progress.tsx +29 -0
  52. package/zzz_react_template/components/ui/radio-group.tsx +43 -0
  53. package/zzz_react_template/components/ui/resizable.tsx +54 -0
  54. package/zzz_react_template/components/ui/scroll-area.tsx +56 -0
  55. package/zzz_react_template/components/ui/select.tsx +185 -0
  56. package/zzz_react_template/components/ui/separator.tsx +26 -0
  57. package/zzz_react_template/components/ui/sheet.tsx +139 -0
  58. package/zzz_react_template/components/ui/sidebar.tsx +734 -0
  59. package/zzz_react_template/components/ui/skeleton.tsx +13 -0
  60. package/zzz_react_template/components/ui/slider.tsx +61 -0
  61. package/zzz_react_template/components/ui/sonner.tsx +23 -0
  62. package/zzz_react_template/components/ui/spinner.tsx +16 -0
  63. package/zzz_react_template/components/ui/switch.tsx +29 -0
  64. package/zzz_react_template/components/ui/table.tsx +114 -0
  65. package/zzz_react_template/components/ui/tabs.tsx +64 -0
  66. package/zzz_react_template/components/ui/textarea.tsx +67 -0
  67. package/zzz_react_template/components/ui/toggle-group.tsx +73 -0
  68. package/zzz_react_template/components/ui/toggle.tsx +45 -0
  69. package/zzz_react_template/components/ui/tooltip.tsx +59 -0
  70. package/zzz_react_template/const.ts +17 -0
  71. package/zzz_react_template/contexts/JXRContext.tsx +264 -0
  72. package/zzz_react_template/contexts/ThemeContext.tsx +64 -0
  73. package/zzz_react_template/hooks/useComposition.ts +81 -0
  74. package/zzz_react_template/hooks/useMobile.tsx +21 -0
  75. package/zzz_react_template/hooks/usePersistFn.ts +20 -0
  76. package/zzz_react_template/index.css +518 -11
  77. package/zzz_react_template/lib/jxr-runtime/index.ts +201 -0
  78. package/zzz_react_template/lib/jxr-runtime/module-resolver.ts +520 -0
  79. package/zzz_react_template/lib/jxr-runtime/moq-transport.ts +267 -0
  80. package/zzz_react_template/lib/jxr-runtime/web-crypto.ts +279 -0
  81. package/zzz_react_template/lib/jxr-runtime/worker-pool.ts +321 -0
  82. package/zzz_react_template/lib/utils.ts +6 -0
  83. package/zzz_react_template/main.tsx +4 -9
  84. package/zzz_react_template/pages/Docs.tsx +955 -0
  85. package/zzz_react_template/pages/Home.tsx +1080 -0
  86. package/zzz_react_template/pages/NotFound.tsx +105 -0
  87. package/zzz_react_template/tsconfig.json +24 -0
@@ -0,0 +1,169 @@
1
+ /**
2
+ * JXR.js — Live Preview Pane
3
+ * Design: LavaFlow OS — Thermal Precision + Edge Command
4
+ * Zero-build React preview rendered in sandboxed iframe
5
+ */
6
+
7
+ import { useRef, useEffect, useState } from 'react';
8
+ import { useJXR } from '@/contexts/JXRContext';
9
+ import { RefreshCw, ExternalLink, Monitor, Smartphone, Tablet, AlertTriangle } from 'lucide-react';
10
+ import { cn } from '@/lib/utils';
11
+
12
+ type ViewportSize = 'desktop' | 'tablet' | 'mobile';
13
+
14
+ const VIEWPORT_WIDTHS: Record<ViewportSize, string> = {
15
+ desktop: '100%',
16
+ tablet: '768px',
17
+ mobile: '375px',
18
+ };
19
+
20
+ export function LivePreview() {
21
+ const { previewHtml, previewState, previewError, refreshPreview } = useJXR();
22
+ const iframeRef = useRef<HTMLIFrameElement>(null);
23
+ const [viewport, setViewport] = useState<ViewportSize>('desktop');
24
+ const [isRefreshing, setIsRefreshing] = useState(false);
25
+
26
+ // Update iframe content when preview HTML changes
27
+ useEffect(() => {
28
+ const iframe = iframeRef.current;
29
+ if (!iframe || !previewHtml) return;
30
+
31
+ const doc = iframe.contentDocument ?? iframe.contentWindow?.document;
32
+ if (!doc) return;
33
+
34
+ doc.open();
35
+ doc.write(previewHtml);
36
+ doc.close();
37
+ }, [previewHtml]);
38
+
39
+ const handleRefresh = () => {
40
+ setIsRefreshing(true);
41
+ refreshPreview();
42
+ setTimeout(() => setIsRefreshing(false), 600);
43
+ };
44
+
45
+ return (
46
+ <div className="flex flex-col h-full bg-background">
47
+ {/* Preview toolbar */}
48
+ <div className="flex items-center justify-between px-3 py-2 border-b border-border bg-card">
49
+ <div className="flex items-center gap-1">
50
+ {/* Viewport toggles */}
51
+ {([
52
+ ['desktop', Monitor],
53
+ ['tablet', Tablet],
54
+ ['mobile', Smartphone],
55
+ ] as [ViewportSize, React.ElementType][]).map(([size, Icon]) => (
56
+ <button
57
+ key={size}
58
+ className={cn(
59
+ 'p-1.5 rounded transition-colors',
60
+ viewport === size
61
+ ? 'bg-lava/20 text-lava'
62
+ : 'text-muted-foreground hover:text-foreground hover:bg-muted/40'
63
+ )}
64
+ onClick={() => setViewport(size)}
65
+ title={`${size} viewport`}
66
+ >
67
+ <Icon className="w-3.5 h-3.5" />
68
+ </button>
69
+ ))}
70
+ </div>
71
+
72
+ {/* URL bar */}
73
+ <div className="flex-1 mx-3">
74
+ <div className="flex items-center gap-2 bg-input rounded px-2 py-1 text-xs font-mono text-muted-foreground">
75
+ <span className="text-success">●</span>
76
+ <span>jxr://preview/localhost</span>
77
+ </div>
78
+ </div>
79
+
80
+ <div className="flex items-center gap-1">
81
+ {/* State indicator */}
82
+ <div className={cn(
83
+ 'flex items-center gap-1.5 text-[10px] px-2 py-1 rounded',
84
+ previewState === 'ready' && 'text-success bg-success/10',
85
+ previewState === 'building' && 'text-warning bg-warning/10',
86
+ previewState === 'error' && 'text-destructive bg-destructive/10',
87
+ previewState === 'idle' && 'text-muted-foreground',
88
+ )}>
89
+ {previewState === 'building' && (
90
+ <RefreshCw className="w-3 h-3 animate-spin" />
91
+ )}
92
+ {previewState === 'ready' && <span className="w-1.5 h-1.5 rounded-full bg-success" />}
93
+ {previewState === 'error' && <AlertTriangle className="w-3 h-3" />}
94
+ <span className="uppercase tracking-wide font-semibold">
95
+ {previewState === 'building' ? 'Building' :
96
+ previewState === 'ready' ? 'Live' :
97
+ previewState === 'error' ? 'Error' : 'Idle'}
98
+ </span>
99
+ </div>
100
+
101
+ <button
102
+ className={cn(
103
+ 'p-1.5 rounded text-muted-foreground hover:text-foreground hover:bg-muted/40 transition-colors',
104
+ isRefreshing && 'text-lava'
105
+ )}
106
+ onClick={handleRefresh}
107
+ title="Refresh preview"
108
+ >
109
+ <RefreshCw className={cn('w-3.5 h-3.5', isRefreshing && 'animate-spin')} />
110
+ </button>
111
+
112
+ <button
113
+ className="p-1.5 rounded text-muted-foreground hover:text-foreground hover:bg-muted/40 transition-colors"
114
+ title="Open in new tab"
115
+ onClick={() => {
116
+ const blob = new Blob([previewHtml], { type: 'text/html' });
117
+ const url = URL.createObjectURL(blob);
118
+ window.open(url, '_blank');
119
+ }}
120
+ >
121
+ <ExternalLink className="w-3.5 h-3.5" />
122
+ </button>
123
+ </div>
124
+ </div>
125
+
126
+ {/* Preview frame */}
127
+ <div className="flex-1 overflow-hidden bg-[#f8f8f8] dark:bg-[#1a1a1a] flex items-start justify-center">
128
+ {previewState === 'error' ? (
129
+ <div className="flex flex-col items-center justify-center h-full gap-4 p-8">
130
+ <AlertTriangle className="w-10 h-10 text-destructive" />
131
+ <div className="text-center">
132
+ <p className="text-sm font-semibold text-destructive mb-2">Preview Error</p>
133
+ <pre className="text-xs text-muted-foreground bg-muted/40 rounded p-3 max-w-sm text-left overflow-auto">
134
+ {previewError}
135
+ </pre>
136
+ </div>
137
+ <button
138
+ className="text-xs text-lava hover:underline"
139
+ onClick={handleRefresh}
140
+ >
141
+ Try again
142
+ </button>
143
+ </div>
144
+ ) : (
145
+ <div
146
+ className="h-full transition-all duration-300 relative"
147
+ style={{ width: VIEWPORT_WIDTHS[viewport] }}
148
+ >
149
+ {/* Viewport border indicator for non-desktop */}
150
+ {viewport !== 'desktop' && (
151
+ <div className="absolute -top-5 left-0 right-0 text-center text-[10px] text-muted-foreground">
152
+ {VIEWPORT_WIDTHS[viewport]}
153
+ </div>
154
+ )}
155
+ <iframe
156
+ ref={iframeRef}
157
+ className={cn(
158
+ 'w-full h-full border-0',
159
+ viewport !== 'desktop' && 'shadow-xl rounded-b-lg'
160
+ )}
161
+ sandbox="allow-scripts allow-same-origin"
162
+ title="JXR Live Preview"
163
+ />
164
+ </div>
165
+ )}
166
+ </div>
167
+ </div>
168
+ );
169
+ }
@@ -0,0 +1,379 @@
1
+ /**
2
+ * JXR.js — Performance Dashboard
3
+ * Design: LavaFlow OS — Thermal Precision + Edge Command
4
+ * Real-time metrics: Worker pool, MoQ transport, module cache
5
+ */
6
+
7
+ import { useState, useEffect, useRef } from 'react';
8
+ import { useJXR } from '@/contexts/JXRContext';
9
+ import type { JXRRuntimeMetrics } from '@/lib/jxr-runtime';
10
+ import {
11
+ Cpu,
12
+ Zap,
13
+ Activity,
14
+ Shield,
15
+ Database,
16
+ Radio,
17
+ TrendingUp,
18
+ Clock,
19
+ } from 'lucide-react';
20
+ import { cn } from '@/lib/utils';
21
+ import { AreaChart, Area, ResponsiveContainer, Tooltip } from 'recharts';
22
+
23
+ // ─── Mini sparkline chart ─────────────────────────────────────────────────────
24
+
25
+ interface SparklineProps {
26
+ data: number[];
27
+ color: string;
28
+ height?: number;
29
+ }
30
+
31
+ function Sparkline({ data, color, height = 32 }: SparklineProps) {
32
+ const chartData = data.map((v, i) => ({ i, v }));
33
+ return (
34
+ <ResponsiveContainer width="100%" height={height}>
35
+ <AreaChart data={chartData} margin={{ top: 2, right: 0, bottom: 0, left: 0 }}>
36
+ <defs>
37
+ <linearGradient id={`grad-${color.replace('#', '')}`} x1="0" y1="0" x2="0" y2="1">
38
+ <stop offset="5%" stopColor={color} stopOpacity={0.3} />
39
+ <stop offset="95%" stopColor={color} stopOpacity={0} />
40
+ </linearGradient>
41
+ </defs>
42
+ <Area
43
+ type="monotone"
44
+ dataKey="v"
45
+ stroke={color}
46
+ strokeWidth={1.5}
47
+ fill={`url(#grad-${color.replace('#', '')})`}
48
+ dot={false}
49
+ isAnimationActive={false}
50
+ />
51
+ <Tooltip
52
+ contentStyle={{ display: 'none' }}
53
+ cursor={false}
54
+ />
55
+ </AreaChart>
56
+ </ResponsiveContainer>
57
+ );
58
+ }
59
+
60
+ // ─── Metric card ──────────────────────────────────────────────────────────────
61
+
62
+ interface MetricCardProps {
63
+ label: string;
64
+ value: string;
65
+ sub?: string;
66
+ icon: React.ReactNode;
67
+ color: 'lava' | 'cyan' | 'green' | 'yellow';
68
+ sparkData?: number[];
69
+ trend?: 'up' | 'down' | 'stable';
70
+ }
71
+
72
+ const COLOR_MAP = {
73
+ lava: { text: 'text-lava', bg: 'bg-lava/10', border: 'border-lava/20', hex: '#e8650a' },
74
+ cyan: { text: 'text-cyan-accent', bg: 'bg-cyan-accent/10', border: 'border-cyan-accent/20', hex: '#22d3ee' },
75
+ green: { text: 'text-success', bg: 'bg-success/10', border: 'border-success/20', hex: '#4ade80' },
76
+ yellow: { text: 'text-warning', bg: 'bg-warning/10', border: 'border-warning/20', hex: '#facc15' },
77
+ };
78
+
79
+ function MetricCard({ label, value, sub, icon, color, sparkData, trend }: MetricCardProps) {
80
+ const c = COLOR_MAP[color];
81
+ return (
82
+ <div className={cn(
83
+ 'rounded-lg border p-3 bg-card',
84
+ c.border
85
+ )}>
86
+ <div className="flex items-start justify-between mb-2">
87
+ <div className={cn('p-1.5 rounded', c.bg)}>
88
+ <div className={c.text}>{icon}</div>
89
+ </div>
90
+ {trend && (
91
+ <TrendingUp className={cn(
92
+ 'w-3 h-3',
93
+ trend === 'up' ? 'text-success' : trend === 'down' ? 'text-destructive' : 'text-muted-foreground'
94
+ )} />
95
+ )}
96
+ </div>
97
+ <div className={cn('text-xl font-bold font-mono', c.text)}>{value}</div>
98
+ <div className="text-[10px] text-muted-foreground uppercase tracking-wide mt-0.5">{label}</div>
99
+ {sub && <div className="text-[10px] text-muted-foreground mt-0.5">{sub}</div>}
100
+ {sparkData && sparkData.length > 0 && (
101
+ <div className="mt-2">
102
+ <Sparkline data={sparkData} color={c.hex} />
103
+ </div>
104
+ )}
105
+ </div>
106
+ );
107
+ }
108
+
109
+ // ─── Worker node visualization ────────────────────────────────────────────────
110
+
111
+ function WorkerNodes({ total, busy }: { total: number; busy: number }) {
112
+ return (
113
+ <div className="flex flex-wrap gap-1.5 mt-2">
114
+ {Array.from({ length: total }).map((_, i) => (
115
+ <div
116
+ key={i}
117
+ className={cn(
118
+ 'w-5 h-5 rounded-sm transition-all duration-300',
119
+ i < busy
120
+ ? 'bg-lava glow-lava animate-pulse'
121
+ : 'bg-muted border border-border'
122
+ )}
123
+ title={i < busy ? 'Busy' : 'Idle'}
124
+ />
125
+ ))}
126
+ </div>
127
+ );
128
+ }
129
+
130
+ // ─── MoQ stream visualizer ────────────────────────────────────────────────────
131
+
132
+ function MoQStreamBar({ bandwidth }: { bandwidth: number }) {
133
+ const maxBw = 1_000_000_000; // 1 Gbps
134
+ const pct = Math.min(100, (bandwidth / maxBw) * 100);
135
+ const mbps = (bandwidth / 1_000_000).toFixed(0);
136
+
137
+ return (
138
+ <div className="space-y-1">
139
+ <div className="flex justify-between text-[10px] text-muted-foreground">
140
+ <span>MoQ Bandwidth</span>
141
+ <span className="font-mono text-cyan-accent">{mbps} Mbps</span>
142
+ </div>
143
+ <div className="h-1.5 bg-muted rounded-full overflow-hidden">
144
+ <div
145
+ className="h-full rounded-full transition-all duration-500"
146
+ style={{
147
+ width: `${pct}%`,
148
+ background: 'linear-gradient(90deg, var(--cyan-accent), var(--lava))',
149
+ }}
150
+ />
151
+ </div>
152
+ </div>
153
+ );
154
+ }
155
+
156
+ // ─── Terminal / Log stream ────────────────────────────────────────────────────
157
+
158
+ export function TerminalPane() {
159
+ const { terminalLines, clearTerminal } = useJXR();
160
+ const bottomRef = useRef<HTMLDivElement>(null);
161
+
162
+ useEffect(() => {
163
+ bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
164
+ }, [terminalLines]);
165
+
166
+ const colorMap = {
167
+ command: 'text-lava',
168
+ info: 'text-muted-foreground',
169
+ success: 'text-success',
170
+ error: 'text-destructive',
171
+ warn: 'text-warning',
172
+ output: 'text-foreground/70',
173
+ };
174
+
175
+ return (
176
+ <div className="flex flex-col h-full bg-[oklch(0.09_0.005_260)] font-mono text-xs">
177
+ <div className="flex items-center justify-between px-3 py-1.5 border-b border-border/50">
178
+ <div className="flex items-center gap-2">
179
+ <span className="text-[10px] uppercase tracking-widest text-muted-foreground">Terminal</span>
180
+ <span className="text-[10px] text-success">● connected</span>
181
+ </div>
182
+ <button
183
+ className="text-[10px] text-muted-foreground hover:text-foreground"
184
+ onClick={clearTerminal}
185
+ >
186
+ Clear
187
+ </button>
188
+ </div>
189
+ <div className="flex-1 overflow-y-auto px-3 py-2 space-y-0.5">
190
+ {terminalLines.map((line) => (
191
+ <div key={line.id} className={cn('leading-relaxed', colorMap[line.type])}>
192
+ {line.type === 'command' ? (
193
+ <span>{line.text}</span>
194
+ ) : (
195
+ <span className="opacity-90">{line.text}</span>
196
+ )}
197
+ </div>
198
+ ))}
199
+ <div ref={bottomRef} />
200
+ </div>
201
+ <div className="flex items-center gap-2 px-3 py-1.5 border-t border-border/50">
202
+ <span className="text-lava">›</span>
203
+ <span className="text-muted-foreground text-[10px]">jxr@edge:~$</span>
204
+ <span className="w-2 h-3.5 bg-lava/70 animate-pulse" />
205
+ </div>
206
+ </div>
207
+ );
208
+ }
209
+
210
+ // ─── Main dashboard ───────────────────────────────────────────────────────────
211
+
212
+ export function PerformanceDashboard() {
213
+ const { metrics, isInitialized } = useJXR();
214
+ const [latencyHistory, setLatencyHistory] = useState<number[]>([]);
215
+ const [throughputHistory, setThroughputHistory] = useState<number[]>([]);
216
+ const [rttHistory, setRttHistory] = useState<number[]>([]);
217
+
218
+ useEffect(() => {
219
+ if (!metrics) return;
220
+ setLatencyHistory((h) => [...h.slice(-30), metrics.workerPool.avgLatencyMs]);
221
+ setThroughputHistory((h) => [...h.slice(-30), metrics.workerPool.throughputPerSec]);
222
+ setRttHistory((h) => [...h.slice(-30), metrics.moq.rttMs]);
223
+ }, [metrics]);
224
+
225
+ const formatUptime = (ms: number) => {
226
+ const s = Math.floor(ms / 1000);
227
+ const m = Math.floor(s / 60);
228
+ const h = Math.floor(m / 60);
229
+ if (h > 0) return `${h}h ${m % 60}m`;
230
+ if (m > 0) return `${m}m ${s % 60}s`;
231
+ return `${s}s`;
232
+ };
233
+
234
+ if (!isInitialized || !metrics) {
235
+ return (
236
+ <div className="flex items-center justify-center h-full">
237
+ <div className="text-center space-y-2">
238
+ <Activity className="w-8 h-8 text-lava animate-pulse mx-auto" />
239
+ <p className="text-xs text-muted-foreground">Initializing runtime...</p>
240
+ </div>
241
+ </div>
242
+ );
243
+ }
244
+
245
+ const { workerPool, moq, moduleCache, uptime } = metrics;
246
+
247
+ return (
248
+ <div className="flex flex-col h-full overflow-y-auto bg-background p-3 space-y-3">
249
+ {/* Header */}
250
+ <div className="flex items-center justify-between">
251
+ <span className="text-[10px] font-semibold uppercase tracking-widest text-muted-foreground">
252
+ Runtime Metrics
253
+ </span>
254
+ <div className="flex items-center gap-1.5 text-[10px] text-success">
255
+ <span className="w-1.5 h-1.5 rounded-full bg-success animate-pulse" />
256
+ <span>Live</span>
257
+ </div>
258
+ </div>
259
+
260
+ {/* Metric cards grid */}
261
+ <div className="grid grid-cols-2 gap-2">
262
+ <MetricCard
263
+ label="Task Throughput"
264
+ value={`${workerPool.throughputPerSec}/s`}
265
+ sub={`${workerPool.totalTasksCompleted} total`}
266
+ icon={<Zap className="w-3.5 h-3.5" />}
267
+ color="lava"
268
+ sparkData={throughputHistory}
269
+ trend="up"
270
+ />
271
+ <MetricCard
272
+ label="Avg Latency"
273
+ value={`${workerPool.avgLatencyMs.toFixed(1)}ms`}
274
+ sub="worker dispatch"
275
+ icon={<Clock className="w-3.5 h-3.5" />}
276
+ color="cyan"
277
+ sparkData={latencyHistory}
278
+ trend="stable"
279
+ />
280
+ <MetricCard
281
+ label="MoQ RTT"
282
+ value={`${moq.rttMs.toFixed(1)}ms`}
283
+ sub={`${(moq.lossRate * 100).toFixed(3)}% loss`}
284
+ icon={<Radio className="w-3.5 h-3.5" />}
285
+ color="green"
286
+ sparkData={rttHistory}
287
+ trend="stable"
288
+ />
289
+ <MetricCard
290
+ label="Module Cache"
291
+ value={`${moduleCache.size}`}
292
+ sub="modules cached"
293
+ icon={<Database className="w-3.5 h-3.5" />}
294
+ color="yellow"
295
+ />
296
+ </div>
297
+
298
+ {/* Worker pool */}
299
+ <div className="rounded-lg border border-border bg-card p-3">
300
+ <div className="flex items-center justify-between mb-2">
301
+ <div className="flex items-center gap-1.5">
302
+ <Cpu className="w-3.5 h-3.5 text-lava" />
303
+ <span className="text-xs font-semibold">Worker Pool</span>
304
+ </div>
305
+ <span className="text-[10px] font-mono text-muted-foreground">
306
+ {workerPool.busyWorkers}/{workerPool.totalWorkers} active
307
+ </span>
308
+ </div>
309
+ <WorkerNodes total={workerPool.totalWorkers} busy={workerPool.busyWorkers} />
310
+ {workerPool.queueDepth > 0 && (
311
+ <div className="mt-2 text-[10px] text-warning">
312
+ Queue depth: {workerPool.queueDepth}
313
+ </div>
314
+ )}
315
+ </div>
316
+
317
+ {/* MoQ transport */}
318
+ <div className="rounded-lg border border-border bg-card p-3 space-y-2">
319
+ <div className="flex items-center gap-1.5 mb-1">
320
+ <Radio className="w-3.5 h-3.5 text-cyan-accent" />
321
+ <span className="text-xs font-semibold">MoQ Transport</span>
322
+ <span className={cn(
323
+ 'ml-auto text-[10px] px-1.5 py-0.5 rounded',
324
+ moq.connectionState === 'connected'
325
+ ? 'bg-success/10 text-success'
326
+ : 'bg-destructive/10 text-destructive'
327
+ )}>
328
+ {moq.connectionState}
329
+ </span>
330
+ </div>
331
+ <MoQStreamBar bandwidth={moq.bandwidthBps} />
332
+ <div className="grid grid-cols-2 gap-2 text-[10px] text-muted-foreground">
333
+ <div>
334
+ <span className="text-foreground font-mono">{(moq.bytesReceived / 1024).toFixed(1)} KB</span>
335
+ <span className="ml-1">received</span>
336
+ </div>
337
+ <div>
338
+ <span className="text-foreground font-mono">{(moq.bytesSent / 1024).toFixed(1)} KB</span>
339
+ <span className="ml-1">sent</span>
340
+ </div>
341
+ </div>
342
+ </div>
343
+
344
+ {/* Crypto status */}
345
+ <div className="rounded-lg border border-cyan-accent/20 bg-card p-3">
346
+ <div className="flex items-center gap-1.5 mb-2">
347
+ <Shield className="w-3.5 h-3.5 text-cyan-accent" />
348
+ <span className="text-xs font-semibold">Web Crypto</span>
349
+ <span className="ml-auto text-[10px] text-success">✓ Active</span>
350
+ </div>
351
+ <div className="space-y-1 text-[10px] text-muted-foreground">
352
+ <div className="flex justify-between">
353
+ <span>Algorithm</span>
354
+ <span className="font-mono text-foreground">AES-GCM-256</span>
355
+ </div>
356
+ <div className="flex justify-between">
357
+ <span>Signing</span>
358
+ <span className="font-mono text-foreground">ECDSA P-256</span>
359
+ </div>
360
+ <div className="flex justify-between">
361
+ <span>KDF</span>
362
+ <span className="font-mono text-foreground">HKDF-SHA256</span>
363
+ </div>
364
+ <div className="flex justify-between">
365
+ <span>Runtime</span>
366
+ <span className="font-mono text-foreground">SubtleCrypto</span>
367
+ </div>
368
+ </div>
369
+ </div>
370
+
371
+ {/* Uptime */}
372
+ <div className="text-center text-[10px] text-muted-foreground pb-1">
373
+ Uptime: <span className="font-mono text-foreground">{formatUptime(uptime)}</span>
374
+ {' · '}
375
+ v{metrics.version}
376
+ </div>
377
+ </div>
378
+ );
379
+ }