@contractspec/lib.example-shared-ui 0.0.0-canary-20260113170453

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.
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@contractspec/lib.example-shared-ui",
3
+ "version": "0.0.0-canary-20260113170453",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./src/index.ts"
7
+ },
8
+ "scripts": {
9
+ "publish:pkg": "bun publish --tolerate-republish --ignore-scripts --verbose",
10
+ "publish:pkg:canary": "bun publish:pkg --tag canary",
11
+ "build": "bun build:types && bun build:bundle",
12
+ "build:bundle": "tsdown",
13
+ "build:types": "tsc --noEmit",
14
+ "dev": "bun build:bundle --watch",
15
+ "clean": "rimraf dist .turbo",
16
+ "lint": "bun lint:fix",
17
+ "lint:fix": "eslint src --fix",
18
+ "lint:check": "eslint src",
19
+ "test": "bun test"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public",
23
+ "registry": "https://registry.npmjs.org/"
24
+ },
25
+ "dependencies": {
26
+ "@apollo/client": "^4.0.12",
27
+ "@contractspec/lib.contracts": "0.0.0-canary-20260113170453",
28
+ "@contractspec/lib.design-system": "0.0.0-canary-20260113170453",
29
+ "@contractspec/lib.ui-kit-web": "0.0.0-canary-20260113170453",
30
+ "@tanstack/react-query": "^5.90.16",
31
+ "framer-motion": "^12.26.1",
32
+ "lucide-react": "^0.300.0",
33
+ "react": "19.2.3",
34
+ "react-dom": "19.2.3"
35
+ },
36
+ "devDependencies": {
37
+ "@contractspec/tool.typescript": "0.0.0-canary-20260113170453",
38
+ "@types/react": "^19.2.8",
39
+ "@types/react-dom": "^19.2.2",
40
+ "eslint": "^9.39.2",
41
+ "typescript": "^5.9.3"
42
+ }
43
+ }
@@ -0,0 +1,480 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useMemo } from 'react';
4
+ import { Button, LoaderBlock } from '@contractspec/lib.design-system';
5
+ import { Card } from '@contractspec/lib.ui-kit-web/ui/card';
6
+ import { Badge } from '@contractspec/lib.ui-kit-web/ui/badge';
7
+ import type { TemplateId } from './lib/types';
8
+ import {
9
+ type OptimizationHint,
10
+ type SpecAnomaly,
11
+ type SpecSuggestion,
12
+ type SpecUsageStats,
13
+ useEvolution,
14
+ } from './hooks/useEvolution';
15
+
16
+ export interface EvolutionDashboardProps {
17
+ templateId: TemplateId;
18
+ onLog?: (message: string) => void;
19
+ }
20
+
21
+ /**
22
+ * Dashboard for AI-powered spec evolution.
23
+ * Shows usage statistics, anomalies, AI suggestions, and optimization hints.
24
+ */
25
+ export function EvolutionDashboard({
26
+ templateId,
27
+ onLog,
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);
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
+ ];
69
+
70
+ for (const op of operations) {
71
+ trackOperation(op.name, op.duration, op.success, op.error);
72
+ }
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]);
81
+
82
+ const handleGenerateSuggestions = useCallback(async () => {
83
+ await generateSuggestions();
84
+ onLog?.('AI suggestions generated');
85
+ }, [generateSuggestions, onLog]);
86
+
87
+ const handleApproveSuggestion = useCallback(
88
+ (id: string) => {
89
+ approveSuggestion(id);
90
+ onLog?.(`Suggestion ${id.slice(0, 8)} approved`);
91
+ },
92
+ [approveSuggestion, onLog]
93
+ );
94
+
95
+ const handleRejectSuggestion = useCallback(
96
+ (id: string) => {
97
+ rejectSuggestion(id);
98
+ onLog?.(`Suggestion ${id.slice(0, 8)} rejected`);
99
+ },
100
+ [rejectSuggestion, onLog]
101
+ );
102
+
103
+ const handleClear = useCallback(() => {
104
+ clear();
105
+ onLog?.('Evolution data cleared');
106
+ }, [clear, onLog]);
107
+
108
+ const pendingSuggestions = useMemo(
109
+ () => suggestions.filter((s) => s.status === 'pending'),
110
+ [suggestions]
111
+ );
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>
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>
163
+
164
+ {loading && <LoaderBlock label="Generating AI suggestions..." />}
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
+ )}
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
+ )}
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
+ )}
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
+ )}
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
+ );
255
+ }
256
+
257
+ /**
258
+ * Usage statistics card component
259
+ */
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
+ );
300
+ }
301
+
302
+ /**
303
+ * Anomaly card component
304
+ */
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
+ };
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
+ );
344
+ }
345
+
346
+ /**
347
+ * Suggestion card component
348
+ */
349
+ function SuggestionCard({
350
+ suggestion,
351
+ onApprove,
352
+ onReject,
353
+ }: {
354
+ suggestion: SpecSuggestion;
355
+ onApprove: (id: string) => void;
356
+ onReject: (id: string) => void;
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
+ };
376
+
377
+ const statusStyle = getStatusStyles(suggestion.status);
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>
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
+ )}
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
+ );
447
+ }
448
+
449
+ /**
450
+ * Optimization hint card component
451
+ */
452
+ function HintCard({ hint }: { hint: OptimizationHint }) {
453
+ const categoryIcons = {
454
+ schema: '📐',
455
+ policy: '🔒',
456
+ performance: '⚡',
457
+ 'error-handling': '🛡️',
458
+ };
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
+ );
480
+ }