@contractspec/lib.example-shared-ui 6.0.5 → 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 (82) hide show
  1. package/.turbo/turbo-build.log +90 -84
  2. package/AGENTS.md +43 -25
  3. package/CHANGELOG.md +11 -0
  4. package/README.md +63 -35
  5. package/dist/EvolutionDashboard.js +9 -9
  6. package/dist/EvolutionSidebar.js +15 -15
  7. package/dist/LocalDataIndicator.js +3 -3
  8. package/dist/MarkdownView.d.ts +0 -7
  9. package/dist/MarkdownView.js +76 -172
  10. package/dist/PersonalizationInsights.js +12 -12
  11. package/dist/SaveToStudioButton.js +2 -2
  12. package/dist/SpecDrivenTemplateShell.d.ts +1 -1
  13. package/dist/SpecDrivenTemplateShell.js +10 -10
  14. package/dist/SpecEditorPanel.js +3 -3
  15. package/dist/TemplateShell.js +10 -10
  16. package/dist/browser/EvolutionDashboard.js +9 -9
  17. package/dist/browser/EvolutionSidebar.js +15 -15
  18. package/dist/browser/LocalDataIndicator.js +3 -3
  19. package/dist/browser/MarkdownView.js +76 -172
  20. package/dist/browser/PersonalizationInsights.js +12 -12
  21. package/dist/browser/SaveToStudioButton.js +2 -2
  22. package/dist/browser/SpecDrivenTemplateShell.js +10 -10
  23. package/dist/browser/SpecEditorPanel.js +3 -3
  24. package/dist/browser/TemplateShell.js +10 -10
  25. package/dist/browser/hooks/index.js +29 -29
  26. package/dist/browser/index.js +193 -286
  27. package/dist/browser/lib/component-registry.js +1 -1
  28. package/dist/browser/markdown/formatPresentationName.js +9 -0
  29. package/dist/browser/markdown/useMarkdownPresentation.js +65 -0
  30. package/dist/hooks/index.d.ts +3 -3
  31. package/dist/hooks/index.js +29 -29
  32. package/dist/index.d.ts +12 -11
  33. package/dist/index.js +193 -286
  34. package/dist/lib/component-registry.js +1 -1
  35. package/dist/markdown/formatPresentationName.d.ts +1 -0
  36. package/dist/markdown/formatPresentationName.js +10 -0
  37. package/dist/markdown/useMarkdownPresentation.d.ts +21 -0
  38. package/dist/markdown/useMarkdownPresentation.js +66 -0
  39. package/dist/node/EvolutionDashboard.js +9 -9
  40. package/dist/node/EvolutionSidebar.js +15 -15
  41. package/dist/node/LocalDataIndicator.js +3 -3
  42. package/dist/node/MarkdownView.js +76 -172
  43. package/dist/node/PersonalizationInsights.js +12 -12
  44. package/dist/node/SaveToStudioButton.js +2 -2
  45. package/dist/node/SpecDrivenTemplateShell.js +10 -10
  46. package/dist/node/SpecEditorPanel.js +3 -3
  47. package/dist/node/TemplateShell.js +10 -10
  48. package/dist/node/hooks/index.js +29 -29
  49. package/dist/node/index.js +193 -286
  50. package/dist/node/lib/component-registry.js +1 -1
  51. package/dist/node/markdown/formatPresentationName.js +9 -0
  52. package/dist/node/markdown/useMarkdownPresentation.js +65 -0
  53. package/dist/utils/index.d.ts +1 -1
  54. package/package.json +40 -13
  55. package/src/EvolutionDashboard.tsx +415 -415
  56. package/src/EvolutionSidebar.tsx +245 -245
  57. package/src/LocalDataIndicator.tsx +28 -28
  58. package/src/MarkdownView.tsx +119 -372
  59. package/src/OverlayContextProvider.tsx +272 -272
  60. package/src/PersonalizationInsights.tsx +232 -232
  61. package/src/SaveToStudioButton.tsx +51 -51
  62. package/src/SpecDrivenTemplateShell.tsx +59 -59
  63. package/src/SpecEditorPanel.tsx +138 -138
  64. package/src/TemplateShell.tsx +50 -50
  65. package/src/bundles/ExampleTemplateBundle.ts +78 -78
  66. package/src/hooks/index.ts +3 -3
  67. package/src/hooks/useBehaviorTracking.ts +252 -252
  68. package/src/hooks/useEvolution.ts +437 -437
  69. package/src/hooks/useRegistryTemplates.ts +42 -42
  70. package/src/hooks/useSpecContent.ts +214 -214
  71. package/src/hooks/useWorkflowComposer.ts +567 -567
  72. package/src/index.ts +12 -11
  73. package/src/lib/component-registry.tsx +40 -40
  74. package/src/lib/runtime-context.tsx +31 -31
  75. package/src/lib/types.ts +57 -57
  76. package/src/markdown/formatPresentationName.ts +9 -0
  77. package/src/markdown/useMarkdownPresentation.ts +107 -0
  78. package/src/overlay-types.ts +15 -15
  79. package/src/utils/fetchPresentationData.ts +13 -13
  80. package/src/utils/generateSpecFromTemplate.ts +29 -29
  81. package/src/utils/index.ts +1 -1
  82. package/tsconfig.json +8 -8
@@ -1,29 +1,24 @@
1
1
  'use client';
2
2
 
3
- import { useCallback, useEffect, useState } from 'react';
4
3
  import {
5
- Button,
6
- ErrorState,
7
- LoaderBlock,
4
+ Button,
5
+ ErrorState,
6
+ LoaderBlock,
7
+ MarkdownRenderer,
8
8
  } from '@contractspec/lib.design-system';
9
- import { Card } from '@contractspec/lib.ui-kit-web/ui/card';
10
9
  import { Badge } from '@contractspec/lib.ui-kit-web/ui/badge';
11
- import type { PresentationTarget } from '@contractspec/lib.contracts-spec/presentations';
12
- import type { TransformEngine } from '@contractspec/lib.contracts-spec/presentations/transform-engine';
13
- import type { TemplateId } from './lib/types';
14
-
10
+ import { Card } from '@contractspec/lib.ui-kit-web/ui/card';
11
+ import { useCallback } from 'react';
15
12
  import { useTemplateRuntime } from './lib/runtime-context';
13
+ import type { TemplateId } from './lib/types';
14
+ import { formatPresentationName } from './markdown/formatPresentationName';
15
+ import { useMarkdownPresentation } from './markdown/useMarkdownPresentation';
16
16
 
17
17
  export interface MarkdownViewProps {
18
- /** Optional override, otherwise comes from context */
19
- templateId?: TemplateId;
20
- presentationId?: string;
21
- className?: string;
22
- }
23
-
24
- interface MarkdownOutput {
25
- mimeType: string;
26
- body: string;
18
+ /** Optional override, otherwise comes from context */
19
+ templateId?: TemplateId;
20
+ presentationId?: string;
21
+ className?: string;
27
22
  }
28
23
 
29
24
  /**
@@ -31,359 +26,111 @@ interface MarkdownOutput {
31
26
  * It allows switching between available presentations for the template.
32
27
  */
33
28
  export function MarkdownView({
34
- templateId: propTemplateId,
35
- presentationId,
36
- className,
29
+ templateId: propTemplateId,
30
+ presentationId,
31
+ className,
37
32
  }: MarkdownViewProps) {
38
- const {
39
- engine,
40
- template,
41
- templateId: contextTemplateId,
42
- resolvePresentation,
43
- fetchData,
44
- } = useTemplateRuntime();
45
-
46
- // Prefer prop if given, else context
47
- const templateId = propTemplateId ?? contextTemplateId;
48
- const presentations = (template?.presentations as string[]) ?? [];
49
-
50
- const [selectedPresentation, setSelectedPresentation] = useState<string>('');
51
- const [markdownContent, setMarkdownContent] = useState<string>('');
52
- const [loading, setLoading] = useState(false);
53
- const [error, setError] = useState<Error | null>(null);
54
-
55
- // Initialize selected presentation
56
- useEffect(() => {
57
- if (presentationId && presentations.includes(presentationId)) {
58
- setSelectedPresentation(presentationId);
59
- } else if (presentations.length > 0 && !selectedPresentation) {
60
- setSelectedPresentation(presentations[0] ?? '');
61
- }
62
- }, [presentationId, presentations, selectedPresentation]);
63
-
64
- // Render markdown when presentation changes
65
- const renderMarkdown = useCallback(async () => {
66
- if (!selectedPresentation || !engine) return;
67
-
68
- setLoading(true);
69
- setError(null);
70
-
71
- try {
72
- if (!resolvePresentation) {
73
- throw new Error('resolvePresentation not available in runtime context');
74
- }
75
-
76
- const descriptor = resolvePresentation(selectedPresentation);
77
-
78
- if (!descriptor) {
79
- throw new Error(
80
- `Presentation descriptor not found: ${selectedPresentation}`
81
- );
82
- }
83
-
84
- // Fetch data for this presentation using the data fetcher from context
85
- const dataResult = await fetchData(selectedPresentation);
86
-
87
- // Render to markdown using the engine with data context
88
- const result = await engine.render<MarkdownOutput>(
89
- 'markdown' as PresentationTarget,
90
- descriptor as Parameters<TransformEngine['render']>[1],
91
- { data: dataResult.data } // Pass data in context for schema-driven rendering
92
- );
93
-
94
- setMarkdownContent(result.body);
95
- } catch (err) {
96
- setError(
97
- err instanceof Error ? err : new Error('Failed to render markdown')
98
- );
99
- } finally {
100
- setLoading(false);
101
- }
102
- }, [
103
- selectedPresentation,
104
- templateId,
105
- engine,
106
- resolvePresentation,
107
- fetchData,
108
- ]);
109
-
110
- useEffect(() => {
111
- renderMarkdown();
112
- }, [renderMarkdown]);
113
-
114
- if (!presentations.length) {
115
- return (
116
- <Card className={className}>
117
- <div className="p-6 text-center">
118
- <p className="text-muted-foreground">
119
- No presentations available for this template.
120
- </p>
121
- </div>
122
- </Card>
123
- );
124
- }
125
-
126
- // Copy markdown to clipboard
127
- const handleCopy = useCallback(() => {
128
- if (markdownContent) {
129
- navigator.clipboard.writeText(markdownContent);
130
- }
131
- }, [markdownContent]);
132
-
133
- return (
134
- <div className={className}>
135
- {/* Presentation Selector */}
136
- <div className="mb-4 flex flex-wrap items-center gap-2">
137
- <span className="text-muted-foreground text-sm font-medium">
138
- Presentation:
139
- </span>
140
- {presentations.map((name) => (
141
- <Button
142
- key={name}
143
- variant={selectedPresentation === name ? 'default' : 'outline'}
144
- size="sm"
145
- onPress={() => setSelectedPresentation(name)}
146
- >
147
- {formatPresentationName(name)}
148
- </Button>
149
- ))}
150
- <div className="ml-auto flex items-center gap-2">
151
- <Badge variant="secondary">LLM-friendly</Badge>
152
- <Button
153
- variant="outline"
154
- size="sm"
155
- onPress={handleCopy}
156
- disabled={!markdownContent || loading}
157
- >
158
- Copy
159
- </Button>
160
- </div>
161
- </div>
162
-
163
- {/* Content Area */}
164
- <Card className="overflow-hidden">
165
- {loading && <LoaderBlock label="Rendering markdown..." />}
166
-
167
- {error && (
168
- <ErrorState
169
- title="Render failed"
170
- description={error.message}
171
- onRetry={renderMarkdown}
172
- retryLabel="Retry"
173
- />
174
- )}
175
-
176
- {!loading && !error && markdownContent && (
177
- <div className="p-6">
178
- <MarkdownRenderer content={markdownContent} />
179
- </div>
180
- )}
181
-
182
- {!loading && !error && !markdownContent && (
183
- <div className="p-6 text-center">
184
- <p className="text-muted-foreground">
185
- Select a presentation to view its markdown output.
186
- </p>
187
- </div>
188
- )}
189
- </Card>
190
- </div>
191
- );
192
- }
193
-
194
- /**
195
- * Simple markdown renderer using pre-formatted display
196
- * For production, consider using react-markdown or similar
197
- */
198
- export function MarkdownRenderer({ content }: { content: string }) {
199
- const lines = content.split('\n');
200
- const rendered: React.ReactNode[] = [];
201
- let i = 0;
202
-
203
- while (i < lines.length) {
204
- const line = lines[i] ?? '';
205
-
206
- // Check for table (starts with | and next line is separator)
207
- if (line.startsWith('|') && lines[i + 1]?.match(/^\|[\s-|]+\|$/)) {
208
- const tableLines: string[] = [line];
209
- i++;
210
- // Collect all table lines
211
- while (i < lines.length && (lines[i]?.startsWith('|') ?? false)) {
212
- tableLines.push(lines[i] ?? '');
213
- i++;
214
- }
215
- rendered.push(renderTable(tableLines, rendered.length));
216
- continue;
217
- }
218
-
219
- // Headers
220
- if (line.startsWith('# ')) {
221
- rendered.push(
222
- <h1 key={i} className="mb-4 text-2xl font-bold">
223
- {line.slice(2)}
224
- </h1>
225
- );
226
- } else if (line.startsWith('## ')) {
227
- rendered.push(
228
- <h2 key={i} className="mt-6 mb-3 text-xl font-semibold">
229
- {line.slice(3)}
230
- </h2>
231
- );
232
- } else if (line.startsWith('### ')) {
233
- rendered.push(
234
- <h3 key={i} className="mt-4 mb-2 text-lg font-medium">
235
- {line.slice(4)}
236
- </h3>
237
- );
238
- }
239
- // Blockquotes
240
- else if (line.startsWith('> ')) {
241
- rendered.push(
242
- <blockquote
243
- key={i}
244
- className="text-muted-foreground my-2 border-l-4 border-violet-500/50 pl-4 italic"
245
- >
246
- {line.slice(2)}
247
- </blockquote>
248
- );
249
- }
250
- // List items
251
- else if (line.startsWith('- ')) {
252
- rendered.push(
253
- <li key={i} className="ml-4 list-disc">
254
- {formatInlineMarkdown(line.slice(2))}
255
- </li>
256
- );
257
- }
258
- // Bold text (lines starting with **)
259
- else if (line.startsWith('**') && line.includes(':**')) {
260
- const [label, ...rest] = line.split(':**');
261
- rendered.push(
262
- <p key={i} className="my-1">
263
- <strong>{label?.slice(2)}:</strong>
264
- {rest.join(':**')}
265
- </p>
266
- );
267
- }
268
- // Italic text (lines starting with _)
269
- else if (line.startsWith('_') && line.endsWith('_')) {
270
- rendered.push(
271
- <p key={i} className="text-muted-foreground my-1 italic">
272
- {line.slice(1, -1)}
273
- </p>
274
- );
275
- }
276
- // Empty lines
277
- else if (!line.trim()) {
278
- rendered.push(<div key={i} className="h-2" />);
279
- }
280
- // Regular text
281
- else {
282
- rendered.push(
283
- <p key={i} className="my-1">
284
- {formatInlineMarkdown(line)}
285
- </p>
286
- );
287
- }
288
-
289
- i++;
290
- }
291
-
292
- return (
293
- <div className="prose prose-sm dark:prose-invert max-w-none">
294
- {rendered}
295
- </div>
296
- );
297
- }
298
-
299
- /**
300
- * Render a markdown table
301
- */
302
- function renderTable(lines: string[], keyPrefix: number): React.ReactNode {
303
- if (lines.length < 2) return null;
304
-
305
- const parseRow = (row: string) =>
306
- row
307
- .split('|')
308
- .slice(1, -1)
309
- .map((cell) => cell.trim());
310
-
311
- const headers = parseRow(lines[0] ?? '');
312
- // Skip separator line (index 1)
313
- const dataRows = lines.slice(2).map(parseRow);
314
-
315
- return (
316
- <div key={`table-${keyPrefix}`} className="my-4 overflow-x-auto">
317
- <table className="border-border min-w-full border-collapse border text-sm">
318
- <thead>
319
- <tr className="bg-muted/50">
320
- {headers.map((header, idx) => (
321
- <th
322
- key={idx}
323
- className="border-border border px-3 py-2 text-left font-semibold"
324
- >
325
- {header}
326
- </th>
327
- ))}
328
- </tr>
329
- </thead>
330
- <tbody>
331
- {dataRows.map((row, rowIdx) => (
332
- <tr key={rowIdx} className="hover:bg-muted/30">
333
- {row.map((cell, cellIdx) => (
334
- <td key={cellIdx} className="border-border border px-3 py-2">
335
- {formatInlineMarkdown(cell)}
336
- </td>
337
- ))}
338
- </tr>
339
- ))}
340
- </tbody>
341
- </table>
342
- </div>
343
- );
344
- }
345
-
346
- /**
347
- * Format inline markdown (bold, code)
348
- */
349
- function formatInlineMarkdown(text: string): React.ReactNode {
350
- // Handle **bold** text
351
- const parts = text.split(/(\*\*[^*]+\*\*)/g);
352
- return parts.map((part, i) => {
353
- if (part.startsWith('**') && part.endsWith('**')) {
354
- return <strong key={i}>{part.slice(2, -2)}</strong>;
355
- }
356
- // Handle `code` text
357
- if (part.includes('`')) {
358
- const codeParts = part.split(/(`[^`]+`)/g);
359
- return codeParts.map((cp, j) => {
360
- if (cp.startsWith('`') && cp.endsWith('`')) {
361
- return (
362
- <code
363
- key={`${i}-${j}`}
364
- className="rounded bg-violet-500/10 px-1.5 py-0.5 font-mono text-sm"
365
- >
366
- {cp.slice(1, -1)}
367
- </code>
368
- );
369
- }
370
- return cp;
371
- });
372
- }
373
- return part;
374
- });
375
- }
376
-
377
- /**
378
- * Format presentation name for display
379
- */
380
- function formatPresentationName(name: string): string {
381
- // Extract the last part after the last dot
382
- const parts = name.split('.');
383
- const lastPart = parts[parts.length - 1] ?? name;
384
- // Convert kebab-case to Title Case
385
- return lastPart
386
- .split('-')
387
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
388
- .join(' ');
33
+ const {
34
+ engine,
35
+ template,
36
+ templateId: contextTemplateId,
37
+ resolvePresentation,
38
+ fetchData,
39
+ } = useTemplateRuntime();
40
+
41
+ const templateId = propTemplateId ?? contextTemplateId;
42
+ const presentations = (template?.presentations as string[]) ?? [];
43
+ const {
44
+ error,
45
+ loading,
46
+ markdownContent,
47
+ renderMarkdown,
48
+ selectedPresentation,
49
+ setSelectedPresentation,
50
+ } = useMarkdownPresentation({
51
+ engine,
52
+ fetchData,
53
+ presentationId,
54
+ presentations,
55
+ resolvePresentation,
56
+ templateId,
57
+ });
58
+
59
+ if (!presentations.length) {
60
+ return (
61
+ <Card className={className}>
62
+ <div className="p-6 text-center">
63
+ <p className="text-muted-foreground">
64
+ No presentations available for this template.
65
+ </p>
66
+ </div>
67
+ </Card>
68
+ );
69
+ }
70
+
71
+ const handleCopy = useCallback(() => {
72
+ if (markdownContent) {
73
+ void navigator.clipboard.writeText(markdownContent);
74
+ }
75
+ }, [markdownContent]);
76
+
77
+ return (
78
+ <div className={className}>
79
+ {/* Presentation Selector */}
80
+ <div className="mb-4 flex flex-wrap items-center gap-2">
81
+ <span className="font-medium text-muted-foreground text-sm">
82
+ Presentation:
83
+ </span>
84
+ {presentations.map((name) => (
85
+ <Button
86
+ key={name}
87
+ variant={selectedPresentation === name ? 'default' : 'outline'}
88
+ size="sm"
89
+ onPress={() => setSelectedPresentation(name)}
90
+ >
91
+ {formatPresentationName(name)}
92
+ </Button>
93
+ ))}
94
+ <div className="ml-auto flex items-center gap-2">
95
+ <Badge variant="secondary">LLM-friendly</Badge>
96
+ <Button
97
+ variant="outline"
98
+ size="sm"
99
+ onPress={handleCopy}
100
+ disabled={!markdownContent || loading}
101
+ >
102
+ Copy
103
+ </Button>
104
+ </div>
105
+ </div>
106
+
107
+ {/* Content Area */}
108
+ <Card className="overflow-hidden">
109
+ {loading && <LoaderBlock label="Rendering markdown..." />}
110
+
111
+ {error && (
112
+ <ErrorState
113
+ title="Render failed"
114
+ description={error.message}
115
+ onRetry={renderMarkdown}
116
+ retryLabel="Retry"
117
+ />
118
+ )}
119
+
120
+ {!loading && !error && markdownContent && (
121
+ <div className="p-6">
122
+ <MarkdownRenderer content={markdownContent} />
123
+ </div>
124
+ )}
125
+
126
+ {!loading && !error && !markdownContent && (
127
+ <div className="p-6 text-center">
128
+ <p className="text-muted-foreground">
129
+ Select a presentation to view its markdown output.
130
+ </p>
131
+ </div>
132
+ )}
133
+ </Card>
134
+ </div>
135
+ );
389
136
  }