@contractspec/lib.example-shared-ui 6.0.6 → 6.0.7

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 (81) hide show
  1. package/.turbo/turbo-build.log +90 -84
  2. package/AGENTS.md +43 -25
  3. package/README.md +63 -35
  4. package/dist/EvolutionDashboard.js +9 -9
  5. package/dist/EvolutionSidebar.js +15 -15
  6. package/dist/LocalDataIndicator.js +3 -3
  7. package/dist/MarkdownView.d.ts +0 -7
  8. package/dist/MarkdownView.js +76 -172
  9. package/dist/PersonalizationInsights.js +12 -12
  10. package/dist/SaveToStudioButton.js +2 -2
  11. package/dist/SpecDrivenTemplateShell.d.ts +1 -1
  12. package/dist/SpecDrivenTemplateShell.js +10 -10
  13. package/dist/SpecEditorPanel.js +3 -3
  14. package/dist/TemplateShell.js +10 -10
  15. package/dist/browser/EvolutionDashboard.js +9 -9
  16. package/dist/browser/EvolutionSidebar.js +15 -15
  17. package/dist/browser/LocalDataIndicator.js +3 -3
  18. package/dist/browser/MarkdownView.js +76 -172
  19. package/dist/browser/PersonalizationInsights.js +12 -12
  20. package/dist/browser/SaveToStudioButton.js +2 -2
  21. package/dist/browser/SpecDrivenTemplateShell.js +10 -10
  22. package/dist/browser/SpecEditorPanel.js +3 -3
  23. package/dist/browser/TemplateShell.js +10 -10
  24. package/dist/browser/hooks/index.js +29 -29
  25. package/dist/browser/index.js +193 -286
  26. package/dist/browser/lib/component-registry.js +1 -1
  27. package/dist/browser/markdown/formatPresentationName.js +9 -0
  28. package/dist/browser/markdown/useMarkdownPresentation.js +65 -0
  29. package/dist/hooks/index.d.ts +3 -3
  30. package/dist/hooks/index.js +29 -29
  31. package/dist/index.d.ts +12 -11
  32. package/dist/index.js +193 -286
  33. package/dist/lib/component-registry.js +1 -1
  34. package/dist/markdown/formatPresentationName.d.ts +1 -0
  35. package/dist/markdown/formatPresentationName.js +10 -0
  36. package/dist/markdown/useMarkdownPresentation.d.ts +21 -0
  37. package/dist/markdown/useMarkdownPresentation.js +66 -0
  38. package/dist/node/EvolutionDashboard.js +9 -9
  39. package/dist/node/EvolutionSidebar.js +15 -15
  40. package/dist/node/LocalDataIndicator.js +3 -3
  41. package/dist/node/MarkdownView.js +76 -172
  42. package/dist/node/PersonalizationInsights.js +12 -12
  43. package/dist/node/SaveToStudioButton.js +2 -2
  44. package/dist/node/SpecDrivenTemplateShell.js +10 -10
  45. package/dist/node/SpecEditorPanel.js +3 -3
  46. package/dist/node/TemplateShell.js +10 -10
  47. package/dist/node/hooks/index.js +29 -29
  48. package/dist/node/index.js +193 -286
  49. package/dist/node/lib/component-registry.js +1 -1
  50. package/dist/node/markdown/formatPresentationName.js +9 -0
  51. package/dist/node/markdown/useMarkdownPresentation.js +65 -0
  52. package/dist/utils/index.d.ts +1 -1
  53. package/package.json +38 -11
  54. package/src/EvolutionDashboard.tsx +415 -415
  55. package/src/EvolutionSidebar.tsx +245 -245
  56. package/src/LocalDataIndicator.tsx +28 -28
  57. package/src/MarkdownView.tsx +119 -372
  58. package/src/OverlayContextProvider.tsx +272 -272
  59. package/src/PersonalizationInsights.tsx +232 -232
  60. package/src/SaveToStudioButton.tsx +51 -51
  61. package/src/SpecDrivenTemplateShell.tsx +59 -59
  62. package/src/SpecEditorPanel.tsx +138 -138
  63. package/src/TemplateShell.tsx +50 -50
  64. package/src/bundles/ExampleTemplateBundle.ts +78 -78
  65. package/src/hooks/index.ts +3 -3
  66. package/src/hooks/useBehaviorTracking.ts +252 -252
  67. package/src/hooks/useEvolution.ts +437 -437
  68. package/src/hooks/useRegistryTemplates.ts +42 -42
  69. package/src/hooks/useSpecContent.ts +214 -214
  70. package/src/hooks/useWorkflowComposer.ts +567 -567
  71. package/src/index.ts +12 -11
  72. package/src/lib/component-registry.tsx +40 -40
  73. package/src/lib/runtime-context.tsx +31 -31
  74. package/src/lib/types.ts +57 -57
  75. package/src/markdown/formatPresentationName.ts +9 -0
  76. package/src/markdown/useMarkdownPresentation.ts +107 -0
  77. package/src/overlay-types.ts +15 -15
  78. package/src/utils/fetchPresentationData.ts +13 -13
  79. package/src/utils/generateSpecFromTemplate.ts +29 -29
  80. package/src/utils/index.ts +1 -1
  81. package/tsconfig.json +8 -8
@@ -1,21 +1,21 @@
1
1
  'use client';
2
2
 
3
- import { useCallback, useMemo } from 'react';
4
3
  import { Button, LoaderBlock } from '@contractspec/lib.design-system';
5
- import { Card } from '@contractspec/lib.ui-kit-web/ui/card';
6
4
  import { Badge } from '@contractspec/lib.ui-kit-web/ui/badge';
7
- import type { TemplateId } from './lib/types';
5
+ import { Card } from '@contractspec/lib.ui-kit-web/ui/card';
6
+ import { useCallback, useMemo } from 'react';
8
7
  import {
9
- type OptimizationHint,
10
- type SpecAnomaly,
11
- type SpecSuggestion,
12
- type SpecUsageStats,
13
- useEvolution,
8
+ type OptimizationHint,
9
+ type SpecAnomaly,
10
+ type SpecSuggestion,
11
+ type SpecUsageStats,
12
+ useEvolution,
14
13
  } from './hooks/useEvolution';
14
+ import type { TemplateId } from './lib/types';
15
15
 
16
16
  export interface EvolutionDashboardProps {
17
- templateId: TemplateId;
18
- onLog?: (message: string) => void;
17
+ templateId: TemplateId;
18
+ onLog?: (message: string) => void;
19
19
  }
20
20
 
21
21
  /**
@@ -23,458 +23,458 @@ export interface EvolutionDashboardProps {
23
23
  * Shows usage statistics, anomalies, AI suggestions, and optimization hints.
24
24
  */
25
25
  export function EvolutionDashboard({
26
- templateId,
27
- onLog,
26
+ templateId,
27
+ onLog,
28
28
  }: EvolutionDashboardProps) {
29
- const {
30
- usageStats,
31
- anomalies,
32
- suggestions,
33
- hints,
34
- loading,
35
- trackOperation,
36
- analyzeUsage,
37
- generateSuggestions,
38
- approveSuggestion,
39
- rejectSuggestion,
40
- clear,
41
- operationCount,
42
- } = useEvolution(templateId);
29
+ const {
30
+ usageStats,
31
+ anomalies,
32
+ suggestions,
33
+ hints,
34
+ loading,
35
+ trackOperation,
36
+ analyzeUsage,
37
+ generateSuggestions,
38
+ approveSuggestion,
39
+ rejectSuggestion,
40
+ clear,
41
+ operationCount,
42
+ } = useEvolution(templateId);
43
43
 
44
- // Simulate operations for demo
45
- const handleSimulateOperations = useCallback(() => {
46
- // Simulate various operations with different outcomes
47
- const operations = [
48
- { name: `${templateId}.list`, duration: 150, success: true },
49
- { name: `${templateId}.list`, duration: 180, success: true },
50
- { name: `${templateId}.create`, duration: 350, success: true },
51
- {
52
- name: `${templateId}.create`,
53
- duration: 420,
54
- success: false,
55
- error: 'VALIDATION_ERROR',
56
- },
57
- { name: `${templateId}.list`, duration: 200, success: true },
58
- { name: `${templateId}.get`, duration: 80, success: true },
59
- { name: `${templateId}.update`, duration: 280, success: true },
60
- { name: `${templateId}.list`, duration: 950, success: true }, // Slow
61
- {
62
- name: `${templateId}.delete`,
63
- duration: 150,
64
- success: false,
65
- error: 'NOT_FOUND',
66
- },
67
- { name: `${templateId}.create`, duration: 380, success: true },
68
- ];
44
+ // Simulate operations for demo
45
+ const handleSimulateOperations = useCallback(() => {
46
+ // Simulate various operations with different outcomes
47
+ const operations = [
48
+ { name: `${templateId}.list`, duration: 150, success: true },
49
+ { name: `${templateId}.list`, duration: 180, success: true },
50
+ { name: `${templateId}.create`, duration: 350, success: true },
51
+ {
52
+ name: `${templateId}.create`,
53
+ duration: 420,
54
+ success: false,
55
+ error: 'VALIDATION_ERROR',
56
+ },
57
+ { name: `${templateId}.list`, duration: 200, success: true },
58
+ { name: `${templateId}.get`, duration: 80, success: true },
59
+ { name: `${templateId}.update`, duration: 280, success: true },
60
+ { name: `${templateId}.list`, duration: 950, success: true }, // Slow
61
+ {
62
+ name: `${templateId}.delete`,
63
+ duration: 150,
64
+ success: false,
65
+ error: 'NOT_FOUND',
66
+ },
67
+ { name: `${templateId}.create`, duration: 380, success: true },
68
+ ];
69
69
 
70
- for (const op of operations) {
71
- trackOperation(op.name, op.duration, op.success, op.error);
72
- }
70
+ for (const op of operations) {
71
+ trackOperation(op.name, op.duration, op.success, op.error);
72
+ }
73
73
 
74
- onLog?.(`Simulated ${operations.length} operations`);
75
- // Auto-analyze after simulation
76
- setTimeout(() => {
77
- analyzeUsage();
78
- onLog?.('Analysis complete');
79
- }, 100);
80
- }, [templateId, trackOperation, analyzeUsage, onLog]);
74
+ onLog?.(`Simulated ${operations.length} operations`);
75
+ // Auto-analyze after simulation
76
+ setTimeout(() => {
77
+ analyzeUsage();
78
+ onLog?.('Analysis complete');
79
+ }, 100);
80
+ }, [templateId, trackOperation, analyzeUsage, onLog]);
81
81
 
82
- const handleGenerateSuggestions = useCallback(async () => {
83
- await generateSuggestions();
84
- onLog?.('AI suggestions generated');
85
- }, [generateSuggestions, onLog]);
82
+ const handleGenerateSuggestions = useCallback(async () => {
83
+ await generateSuggestions();
84
+ onLog?.('AI suggestions generated');
85
+ }, [generateSuggestions, onLog]);
86
86
 
87
- const handleApproveSuggestion = useCallback(
88
- (id: string) => {
89
- approveSuggestion(id);
90
- onLog?.(`Suggestion ${id.slice(0, 8)} approved`);
91
- },
92
- [approveSuggestion, onLog]
93
- );
87
+ const handleApproveSuggestion = useCallback(
88
+ (id: string) => {
89
+ approveSuggestion(id);
90
+ onLog?.(`Suggestion ${id.slice(0, 8)} approved`);
91
+ },
92
+ [approveSuggestion, onLog]
93
+ );
94
94
 
95
- const handleRejectSuggestion = useCallback(
96
- (id: string) => {
97
- rejectSuggestion(id);
98
- onLog?.(`Suggestion ${id.slice(0, 8)} rejected`);
99
- },
100
- [rejectSuggestion, onLog]
101
- );
95
+ const handleRejectSuggestion = useCallback(
96
+ (id: string) => {
97
+ rejectSuggestion(id);
98
+ onLog?.(`Suggestion ${id.slice(0, 8)} rejected`);
99
+ },
100
+ [rejectSuggestion, onLog]
101
+ );
102
102
 
103
- const handleClear = useCallback(() => {
104
- clear();
105
- onLog?.('Evolution data cleared');
106
- }, [clear, onLog]);
103
+ const handleClear = useCallback(() => {
104
+ clear();
105
+ onLog?.('Evolution data cleared');
106
+ }, [clear, onLog]);
107
107
 
108
- const pendingSuggestions = useMemo(
109
- () => suggestions.filter((s) => s.status === 'pending'),
110
- [suggestions]
111
- );
108
+ const pendingSuggestions = useMemo(
109
+ () => suggestions.filter((s) => s.status === 'pending'),
110
+ [suggestions]
111
+ );
112
112
 
113
- return (
114
- <div className="space-y-6">
115
- {/* Header */}
116
- <div className="flex items-center justify-between">
117
- <div>
118
- <h2 className="text-xl font-semibold">AI Evolution Engine</h2>
119
- <p className="text-muted-foreground text-sm">
120
- Analyze usage patterns and get AI-powered suggestions
121
- </p>
122
- </div>
123
- <div className="flex items-center gap-2">
124
- <Badge variant="secondary">{operationCount} ops tracked</Badge>
125
- <Button variant="ghost" size="sm" onPress={handleClear}>
126
- Clear
127
- </Button>
128
- </div>
129
- </div>
113
+ return (
114
+ <div className="space-y-6">
115
+ {/* Header */}
116
+ <div className="flex items-center justify-between">
117
+ <div>
118
+ <h2 className="font-semibold text-xl">AI Evolution Engine</h2>
119
+ <p className="text-muted-foreground text-sm">
120
+ Analyze usage patterns and get AI-powered suggestions
121
+ </p>
122
+ </div>
123
+ <div className="flex items-center gap-2">
124
+ <Badge variant="secondary">{operationCount} ops tracked</Badge>
125
+ <Button variant="ghost" size="sm" onPress={handleClear}>
126
+ Clear
127
+ </Button>
128
+ </div>
129
+ </div>
130
130
 
131
- {/* Actions */}
132
- <Card className="p-4">
133
- <div className="flex flex-wrap items-center gap-3">
134
- <Button
135
- variant="default"
136
- size="sm"
137
- onPress={handleSimulateOperations}
138
- >
139
- Simulate Operations
140
- </Button>
141
- <Button
142
- variant="outline"
143
- size="sm"
144
- onPress={analyzeUsage}
145
- disabled={operationCount < 5}
146
- >
147
- Analyze Usage
148
- </Button>
149
- <Button
150
- variant="outline"
151
- size="sm"
152
- onPress={handleGenerateSuggestions}
153
- disabled={anomalies.length === 0 || loading}
154
- >
155
- {loading ? 'Generating...' : 'Generate AI Suggestions'}
156
- </Button>
157
- </div>
158
- <p className="text-muted-foreground mt-2 text-xs">
159
- Simulate sandbox operations, analyze patterns, and generate AI
160
- improvement suggestions.
161
- </p>
162
- </Card>
131
+ {/* Actions */}
132
+ <Card className="p-4">
133
+ <div className="flex flex-wrap items-center gap-3">
134
+ <Button
135
+ variant="default"
136
+ size="sm"
137
+ onPress={handleSimulateOperations}
138
+ >
139
+ Simulate Operations
140
+ </Button>
141
+ <Button
142
+ variant="outline"
143
+ size="sm"
144
+ onPress={analyzeUsage}
145
+ disabled={operationCount < 5}
146
+ >
147
+ Analyze Usage
148
+ </Button>
149
+ <Button
150
+ variant="outline"
151
+ size="sm"
152
+ onPress={handleGenerateSuggestions}
153
+ disabled={anomalies.length === 0 || loading}
154
+ >
155
+ {loading ? 'Generating...' : 'Generate AI Suggestions'}
156
+ </Button>
157
+ </div>
158
+ <p className="mt-2 text-muted-foreground text-xs">
159
+ Simulate sandbox operations, analyze patterns, and generate AI
160
+ improvement suggestions.
161
+ </p>
162
+ </Card>
163
163
 
164
- {loading && <LoaderBlock label="Generating AI suggestions..." />}
164
+ {loading && <LoaderBlock label="Generating AI suggestions..." />}
165
165
 
166
- {/* Usage Statistics */}
167
- {usageStats.length > 0 && (
168
- <Card className="p-4">
169
- <h3 className="mb-3 font-semibold">Usage Statistics</h3>
170
- <div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
171
- {usageStats.map((stat) => (
172
- <UsageStatCard key={stat.operation.name} stat={stat} />
173
- ))}
174
- </div>
175
- </Card>
176
- )}
166
+ {/* Usage Statistics */}
167
+ {usageStats.length > 0 && (
168
+ <Card className="p-4">
169
+ <h3 className="mb-3 font-semibold">Usage Statistics</h3>
170
+ <div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
171
+ {usageStats.map((stat) => (
172
+ <UsageStatCard key={stat.operation.name} stat={stat} />
173
+ ))}
174
+ </div>
175
+ </Card>
176
+ )}
177
177
 
178
- {/* Anomalies */}
179
- {anomalies.length > 0 && (
180
- <Card className="p-4">
181
- <div className="mb-3 flex items-center justify-between">
182
- <h3 className="font-semibold">Detected Anomalies</h3>
183
- <Badge
184
- variant="secondary"
185
- className="border-amber-500/30 bg-amber-500/20 text-amber-400"
186
- >
187
- {anomalies.length} issues
188
- </Badge>
189
- </div>
190
- <div className="space-y-2">
191
- {anomalies.map((anomaly, index) => (
192
- <AnomalyCard
193
- key={`${anomaly.operation.name}-${index}`}
194
- anomaly={anomaly}
195
- />
196
- ))}
197
- </div>
198
- </Card>
199
- )}
178
+ {/* Anomalies */}
179
+ {anomalies.length > 0 && (
180
+ <Card className="p-4">
181
+ <div className="mb-3 flex items-center justify-between">
182
+ <h3 className="font-semibold">Detected Anomalies</h3>
183
+ <Badge
184
+ variant="secondary"
185
+ className="border-amber-500/30 bg-amber-500/20 text-amber-400"
186
+ >
187
+ {anomalies.length} issues
188
+ </Badge>
189
+ </div>
190
+ <div className="space-y-2">
191
+ {anomalies.map((anomaly, index) => (
192
+ <AnomalyCard
193
+ key={`${anomaly.operation.name}-${index}`}
194
+ anomaly={anomaly}
195
+ />
196
+ ))}
197
+ </div>
198
+ </Card>
199
+ )}
200
200
 
201
- {/* AI Suggestions */}
202
- {suggestions.length > 0 && (
203
- <Card className="p-4">
204
- <div className="mb-3 flex items-center justify-between">
205
- <h3 className="font-semibold">AI Suggestions</h3>
206
- <div className="flex items-center gap-2">
207
- {pendingSuggestions.length > 0 && (
208
- <Badge
209
- variant="secondary"
210
- className="border-amber-500/30 bg-amber-500/20 text-amber-400"
211
- >
212
- {pendingSuggestions.length} pending
213
- </Badge>
214
- )}
215
- </div>
216
- </div>
217
- <div className="space-y-3">
218
- {suggestions.map((suggestion) => (
219
- <SuggestionCard
220
- key={suggestion.id}
221
- suggestion={suggestion}
222
- onApprove={handleApproveSuggestion}
223
- onReject={handleRejectSuggestion}
224
- />
225
- ))}
226
- </div>
227
- </Card>
228
- )}
201
+ {/* AI Suggestions */}
202
+ {suggestions.length > 0 && (
203
+ <Card className="p-4">
204
+ <div className="mb-3 flex items-center justify-between">
205
+ <h3 className="font-semibold">AI Suggestions</h3>
206
+ <div className="flex items-center gap-2">
207
+ {pendingSuggestions.length > 0 && (
208
+ <Badge
209
+ variant="secondary"
210
+ className="border-amber-500/30 bg-amber-500/20 text-amber-400"
211
+ >
212
+ {pendingSuggestions.length} pending
213
+ </Badge>
214
+ )}
215
+ </div>
216
+ </div>
217
+ <div className="space-y-3">
218
+ {suggestions.map((suggestion) => (
219
+ <SuggestionCard
220
+ key={suggestion.id}
221
+ suggestion={suggestion}
222
+ onApprove={handleApproveSuggestion}
223
+ onReject={handleRejectSuggestion}
224
+ />
225
+ ))}
226
+ </div>
227
+ </Card>
228
+ )}
229
229
 
230
- {/* Optimization Hints */}
231
- {hints.length > 0 && (
232
- <Card className="p-4">
233
- <h3 className="mb-3 font-semibold">Optimization Hints</h3>
234
- <div className="space-y-2">
235
- {hints.map((hint, index) => (
236
- <HintCard key={`${hint.operation.name}-${index}`} hint={hint} />
237
- ))}
238
- </div>
239
- </Card>
240
- )}
230
+ {/* Optimization Hints */}
231
+ {hints.length > 0 && (
232
+ <Card className="p-4">
233
+ <h3 className="mb-3 font-semibold">Optimization Hints</h3>
234
+ <div className="space-y-2">
235
+ {hints.map((hint, index) => (
236
+ <HintCard key={`${hint.operation.name}-${index}`} hint={hint} />
237
+ ))}
238
+ </div>
239
+ </Card>
240
+ )}
241
241
 
242
- {/* Empty State */}
243
- {usageStats.length === 0 &&
244
- anomalies.length === 0 &&
245
- suggestions.length === 0 && (
246
- <Card className="p-8 text-center">
247
- <p className="text-muted-foreground">
248
- Click &quot;Simulate Operations&quot; to generate sample data for
249
- analysis.
250
- </p>
251
- </Card>
252
- )}
253
- </div>
254
- );
242
+ {/* Empty State */}
243
+ {usageStats.length === 0 &&
244
+ anomalies.length === 0 &&
245
+ suggestions.length === 0 && (
246
+ <Card className="p-8 text-center">
247
+ <p className="text-muted-foreground">
248
+ Click &quot;Simulate Operations&quot; to generate sample data for
249
+ analysis.
250
+ </p>
251
+ </Card>
252
+ )}
253
+ </div>
254
+ );
255
255
  }
256
256
 
257
257
  /**
258
258
  * Usage statistics card component
259
259
  */
260
260
  function UsageStatCard({ stat }: { stat: SpecUsageStats }) {
261
- return (
262
- <div className="rounded-lg border border-violet-500/20 bg-violet-500/5 p-3">
263
- <div className="mb-2 flex items-center justify-between">
264
- <span className="font-mono text-sm font-medium">
265
- {stat.operation.name}
266
- </span>
267
- <Badge
268
- variant={stat.errorRate > 0.1 ? 'destructive' : 'default'}
269
- className={
270
- stat.errorRate > 0.1
271
- ? ''
272
- : 'border-green-500/30 bg-green-500/20 text-green-400'
273
- }
274
- >
275
- {((1 - stat.errorRate) * 100).toFixed(0)}% success
276
- </Badge>
277
- </div>
278
- <div className="grid grid-cols-2 gap-2 text-xs">
279
- <div>
280
- <span className="text-muted-foreground">Total Calls:</span>{' '}
281
- <span className="font-medium">{stat.totalCalls}</span>
282
- </div>
283
- <div>
284
- <span className="text-muted-foreground">Avg Latency:</span>{' '}
285
- <span className="font-medium">
286
- {stat.averageLatencyMs.toFixed(0)}ms
287
- </span>
288
- </div>
289
- <div>
290
- <span className="text-muted-foreground">P95:</span>{' '}
291
- <span className="font-medium">{stat.p95LatencyMs.toFixed(0)}ms</span>
292
- </div>
293
- <div>
294
- <span className="text-muted-foreground">P99:</span>{' '}
295
- <span className="font-medium">{stat.p99LatencyMs.toFixed(0)}ms</span>
296
- </div>
297
- </div>
298
- </div>
299
- );
261
+ return (
262
+ <div className="rounded-lg border border-violet-500/20 bg-violet-500/5 p-3">
263
+ <div className="mb-2 flex items-center justify-between">
264
+ <span className="font-medium font-mono text-sm">
265
+ {stat.operation.name}
266
+ </span>
267
+ <Badge
268
+ variant={stat.errorRate > 0.1 ? 'destructive' : 'default'}
269
+ className={
270
+ stat.errorRate > 0.1
271
+ ? ''
272
+ : 'border-green-500/30 bg-green-500/20 text-green-400'
273
+ }
274
+ >
275
+ {((1 - stat.errorRate) * 100).toFixed(0)}% success
276
+ </Badge>
277
+ </div>
278
+ <div className="grid grid-cols-2 gap-2 text-xs">
279
+ <div>
280
+ <span className="text-muted-foreground">Total Calls:</span>{' '}
281
+ <span className="font-medium">{stat.totalCalls}</span>
282
+ </div>
283
+ <div>
284
+ <span className="text-muted-foreground">Avg Latency:</span>{' '}
285
+ <span className="font-medium">
286
+ {stat.averageLatencyMs.toFixed(0)}ms
287
+ </span>
288
+ </div>
289
+ <div>
290
+ <span className="text-muted-foreground">P95:</span>{' '}
291
+ <span className="font-medium">{stat.p95LatencyMs.toFixed(0)}ms</span>
292
+ </div>
293
+ <div>
294
+ <span className="text-muted-foreground">P99:</span>{' '}
295
+ <span className="font-medium">{stat.p99LatencyMs.toFixed(0)}ms</span>
296
+ </div>
297
+ </div>
298
+ </div>
299
+ );
300
300
  }
301
301
 
302
302
  /**
303
303
  * Anomaly card component
304
304
  */
305
305
  function AnomalyCard({ anomaly }: { anomaly: SpecAnomaly }) {
306
- const severityColors = {
307
- low: 'text-amber-400',
308
- medium: 'text-orange-400',
309
- high: 'text-red-400',
310
- };
306
+ const severityColors = {
307
+ low: 'text-amber-400',
308
+ medium: 'text-orange-400',
309
+ high: 'text-red-400',
310
+ };
311
311
 
312
- return (
313
- <div className="flex items-center justify-between rounded-lg border border-amber-500/30 bg-amber-500/5 p-3">
314
- <div className="flex items-center gap-3">
315
- <span
316
- className={`text-lg ${severityColors[anomaly.severity]}`}
317
- title={`${anomaly.severity} severity`}
318
- >
319
- {anomaly.severity === 'high'
320
- ? '🔴'
321
- : anomaly.severity === 'medium'
322
- ? '🟠'
323
- : '🟡'}
324
- </span>
325
- <div>
326
- <p className="text-sm font-medium">{anomaly.description}</p>
327
- <p className="text-muted-foreground text-xs">
328
- {anomaly.operation.name} • {anomaly.metric}
329
- </p>
330
- </div>
331
- </div>
332
- <Badge
333
- variant={anomaly.severity === 'high' ? 'destructive' : 'secondary'}
334
- className={
335
- anomaly.severity === 'medium'
336
- ? 'border-amber-500/30 bg-amber-500/20 text-amber-400'
337
- : ''
338
- }
339
- >
340
- {anomaly.severity}
341
- </Badge>
342
- </div>
343
- );
312
+ return (
313
+ <div className="flex items-center justify-between rounded-lg border border-amber-500/30 bg-amber-500/5 p-3">
314
+ <div className="flex items-center gap-3">
315
+ <span
316
+ className={`text-lg ${severityColors[anomaly.severity]}`}
317
+ title={`${anomaly.severity} severity`}
318
+ >
319
+ {anomaly.severity === 'high'
320
+ ? '🔴'
321
+ : anomaly.severity === 'medium'
322
+ ? '🟠'
323
+ : '🟡'}
324
+ </span>
325
+ <div>
326
+ <p className="font-medium text-sm">{anomaly.description}</p>
327
+ <p className="text-muted-foreground text-xs">
328
+ {anomaly.operation.name} • {anomaly.metric}
329
+ </p>
330
+ </div>
331
+ </div>
332
+ <Badge
333
+ variant={anomaly.severity === 'high' ? 'destructive' : 'secondary'}
334
+ className={
335
+ anomaly.severity === 'medium'
336
+ ? 'border-amber-500/30 bg-amber-500/20 text-amber-400'
337
+ : ''
338
+ }
339
+ >
340
+ {anomaly.severity}
341
+ </Badge>
342
+ </div>
343
+ );
344
344
  }
345
345
 
346
346
  /**
347
347
  * Suggestion card component
348
348
  */
349
349
  function SuggestionCard({
350
- suggestion,
351
- onApprove,
352
- onReject,
350
+ suggestion,
351
+ onApprove,
352
+ onReject,
353
353
  }: {
354
- suggestion: SpecSuggestion;
355
- onApprove: (id: string) => void;
356
- onReject: (id: string) => void;
354
+ suggestion: SpecSuggestion;
355
+ onApprove: (id: string) => void;
356
+ onReject: (id: string) => void;
357
357
  }) {
358
- const getStatusStyles = (status: string) => {
359
- switch (status) {
360
- case 'pending':
361
- return {
362
- variant: 'secondary' as const,
363
- className: 'bg-amber-500/20 text-amber-400 border-amber-500/30',
364
- };
365
- case 'approved':
366
- return {
367
- variant: 'default' as const,
368
- className: 'bg-green-500/20 text-green-400 border-green-500/30',
369
- };
370
- case 'rejected':
371
- return { variant: 'destructive' as const, className: '' };
372
- default:
373
- return { variant: 'secondary' as const, className: '' };
374
- }
375
- };
358
+ const getStatusStyles = (status: string) => {
359
+ switch (status) {
360
+ case 'pending':
361
+ return {
362
+ variant: 'secondary' as const,
363
+ className: 'bg-amber-500/20 text-amber-400 border-amber-500/30',
364
+ };
365
+ case 'approved':
366
+ return {
367
+ variant: 'default' as const,
368
+ className: 'bg-green-500/20 text-green-400 border-green-500/30',
369
+ };
370
+ case 'rejected':
371
+ return { variant: 'destructive' as const, className: '' };
372
+ default:
373
+ return { variant: 'secondary' as const, className: '' };
374
+ }
375
+ };
376
376
 
377
- const statusStyle = getStatusStyles(suggestion.status);
377
+ const statusStyle = getStatusStyles(suggestion.status);
378
378
 
379
- return (
380
- <div className="rounded-lg border border-violet-500/30 bg-violet-500/5 p-4">
381
- <div className="mb-2 flex items-start justify-between">
382
- <div className="flex-1">
383
- <div className="flex items-center gap-2">
384
- <span className="text-lg">
385
- {suggestion.intent.type === 'latency-regression'
386
- ? '⚡'
387
- : suggestion.intent.type === 'error-spike'
388
- ? '🔥'
389
- : '📉'}
390
- </span>
391
- <h4 className="font-medium">{suggestion.proposal.summary}</h4>
392
- </div>
393
- <p className="text-muted-foreground mt-1 text-sm">
394
- {suggestion.proposal.rationale}
395
- </p>
396
- </div>
397
- <Badge variant={statusStyle.variant} className={statusStyle.className}>
398
- {suggestion.status}
399
- </Badge>
400
- </div>
379
+ return (
380
+ <div className="rounded-lg border border-violet-500/30 bg-violet-500/5 p-4">
381
+ <div className="mb-2 flex items-start justify-between">
382
+ <div className="flex-1">
383
+ <div className="flex items-center gap-2">
384
+ <span className="text-lg">
385
+ {suggestion.intent.type === 'latency-regression'
386
+ ? '⚡'
387
+ : suggestion.intent.type === 'error-spike'
388
+ ? '🔥'
389
+ : '📉'}
390
+ </span>
391
+ <h4 className="font-medium">{suggestion.proposal.summary}</h4>
392
+ </div>
393
+ <p className="mt-1 text-muted-foreground text-sm">
394
+ {suggestion.proposal.rationale}
395
+ </p>
396
+ </div>
397
+ <Badge variant={statusStyle.variant} className={statusStyle.className}>
398
+ {suggestion.status}
399
+ </Badge>
400
+ </div>
401
401
 
402
- {suggestion.proposal.recommendedActions &&
403
- suggestion.proposal.recommendedActions.length > 0 && (
404
- <div className="mt-3">
405
- <p className="mb-1 text-xs font-semibold text-violet-400 uppercase">
406
- Recommended Actions
407
- </p>
408
- <ul className="list-inside list-disc space-y-1 text-xs">
409
- {suggestion.proposal.recommendedActions
410
- .slice(0, 3)
411
- .map((action, i) => (
412
- <li key={i}>{action}</li>
413
- ))}
414
- </ul>
415
- </div>
416
- )}
402
+ {suggestion.proposal.recommendedActions &&
403
+ suggestion.proposal.recommendedActions.length > 0 && (
404
+ <div className="mt-3">
405
+ <p className="mb-1 font-semibold text-violet-400 text-xs uppercase">
406
+ Recommended Actions
407
+ </p>
408
+ <ul className="list-inside list-disc space-y-1 text-xs">
409
+ {suggestion.proposal.recommendedActions
410
+ .slice(0, 3)
411
+ .map((action, i) => (
412
+ <li key={i}>{action}</li>
413
+ ))}
414
+ </ul>
415
+ </div>
416
+ )}
417
417
 
418
- <div className="mt-3 flex items-center justify-between">
419
- <div className="flex items-center gap-2 text-xs">
420
- <span className="text-muted-foreground">
421
- Confidence: {(suggestion.confidence * 100).toFixed(0)}%
422
- </span>
423
- <span className="text-muted-foreground">•</span>
424
- <Badge variant="secondary">{suggestion.priority}</Badge>
425
- </div>
426
- {suggestion.status === 'pending' && (
427
- <div className="flex items-center gap-2">
428
- <Button
429
- variant="outline"
430
- size="sm"
431
- onPress={() => onReject(suggestion.id)}
432
- >
433
- Reject
434
- </Button>
435
- <Button
436
- variant="default"
437
- size="sm"
438
- onPress={() => onApprove(suggestion.id)}
439
- >
440
- Approve
441
- </Button>
442
- </div>
443
- )}
444
- </div>
445
- </div>
446
- );
418
+ <div className="mt-3 flex items-center justify-between">
419
+ <div className="flex items-center gap-2 text-xs">
420
+ <span className="text-muted-foreground">
421
+ Confidence: {(suggestion.confidence * 100).toFixed(0)}%
422
+ </span>
423
+ <span className="text-muted-foreground">•</span>
424
+ <Badge variant="secondary">{suggestion.priority}</Badge>
425
+ </div>
426
+ {suggestion.status === 'pending' && (
427
+ <div className="flex items-center gap-2">
428
+ <Button
429
+ variant="outline"
430
+ size="sm"
431
+ onPress={() => onReject(suggestion.id)}
432
+ >
433
+ Reject
434
+ </Button>
435
+ <Button
436
+ variant="default"
437
+ size="sm"
438
+ onPress={() => onApprove(suggestion.id)}
439
+ >
440
+ Approve
441
+ </Button>
442
+ </div>
443
+ )}
444
+ </div>
445
+ </div>
446
+ );
447
447
  }
448
448
 
449
449
  /**
450
450
  * Optimization hint card component
451
451
  */
452
452
  function HintCard({ hint }: { hint: OptimizationHint }) {
453
- const categoryIcons = {
454
- schema: '📐',
455
- policy: '🔒',
456
- performance: '⚡',
457
- 'error-handling': '🛡️',
458
- };
453
+ const categoryIcons = {
454
+ schema: '📐',
455
+ policy: '🔒',
456
+ performance: '⚡',
457
+ 'error-handling': '🛡️',
458
+ };
459
459
 
460
- return (
461
- <div className="rounded-lg border border-blue-500/20 bg-blue-500/5 p-3">
462
- <div className="flex items-start gap-3">
463
- <span className="text-lg">{categoryIcons[hint.category]}</span>
464
- <div className="flex-1">
465
- <p className="font-medium">{hint.summary}</p>
466
- <p className="text-muted-foreground mt-1 text-xs">
467
- {hint.justification}
468
- </p>
469
- {hint.recommendedActions.length > 0 && (
470
- <ul className="mt-2 list-inside list-disc text-xs">
471
- {hint.recommendedActions.slice(0, 2).map((action, i) => (
472
- <li key={i}>{action}</li>
473
- ))}
474
- </ul>
475
- )}
476
- </div>
477
- </div>
478
- </div>
479
- );
460
+ return (
461
+ <div className="rounded-lg border border-blue-500/20 bg-blue-500/5 p-3">
462
+ <div className="flex items-start gap-3">
463
+ <span className="text-lg">{categoryIcons[hint.category]}</span>
464
+ <div className="flex-1">
465
+ <p className="font-medium">{hint.summary}</p>
466
+ <p className="mt-1 text-muted-foreground text-xs">
467
+ {hint.justification}
468
+ </p>
469
+ {hint.recommendedActions.length > 0 && (
470
+ <ul className="mt-2 list-inside list-disc text-xs">
471
+ {hint.recommendedActions.slice(0, 2).map((action, i) => (
472
+ <li key={i}>{action}</li>
473
+ ))}
474
+ </ul>
475
+ )}
476
+ </div>
477
+ </div>
478
+ </div>
479
+ );
480
480
  }