@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/.turbo/turbo-build$colon$bundle.log +9 -0
- package/.turbo/turbo-build.log +11 -0
- package/CHANGELOG.md +34 -0
- package/dist/index.mjs +3121 -0
- package/package.json +43 -0
- package/src/EvolutionDashboard.tsx +480 -0
- package/src/EvolutionSidebar.tsx +282 -0
- package/src/LocalDataIndicator.tsx +39 -0
- package/src/MarkdownView.tsx +389 -0
- package/src/OverlayContextProvider.tsx +341 -0
- package/src/PersonalizationInsights.tsx +293 -0
- package/src/SaveToStudioButton.tsx +64 -0
- package/src/SpecEditorPanel.tsx +165 -0
- package/src/TemplateShell.tsx +63 -0
- package/src/hooks/index.ts +5 -0
- package/src/hooks/useBehaviorTracking.ts +327 -0
- package/src/hooks/useEvolution.ts +501 -0
- package/src/hooks/useRegistryTemplates.ts +49 -0
- package/src/hooks/useSpecContent.ts +243 -0
- package/src/hooks/useWorkflowComposer.ts +670 -0
- package/src/index.ts +15 -0
- package/src/lib/component-registry.tsx +64 -0
- package/src/lib/runtime-context.tsx +54 -0
- package/src/lib/types.ts +84 -0
- package/src/overlay-types.ts +25 -0
- package/src/utils/fetchPresentationData.ts +48 -0
- package/src/utils/generateSpecFromTemplate.ts +458 -0
- package/src/utils/index.ts +2 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import type { TemplateId } from '../lib/types';
|
|
5
|
+
import { useTemplateRuntime } from '../lib/runtime-context';
|
|
6
|
+
import { generateSpecFromTemplate } from '../utils/generateSpecFromTemplate';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Storage key prefix for spec content persistence
|
|
10
|
+
*/
|
|
11
|
+
const SPEC_STORAGE_KEY = 'contractspec-spec-content';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Validation result for spec content
|
|
15
|
+
*/
|
|
16
|
+
export interface SpecValidationResult {
|
|
17
|
+
valid: boolean;
|
|
18
|
+
errors: {
|
|
19
|
+
line: number;
|
|
20
|
+
message: string;
|
|
21
|
+
severity: 'error' | 'warning';
|
|
22
|
+
}[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Hook return type
|
|
27
|
+
*/
|
|
28
|
+
export interface UseSpecContentReturn {
|
|
29
|
+
/** Current spec content */
|
|
30
|
+
content: string;
|
|
31
|
+
/** Whether the spec is loading */
|
|
32
|
+
loading: boolean;
|
|
33
|
+
/** Whether the spec has unsaved changes */
|
|
34
|
+
isDirty: boolean;
|
|
35
|
+
/** Last validation result */
|
|
36
|
+
validation: SpecValidationResult | null;
|
|
37
|
+
/** Update spec content */
|
|
38
|
+
setContent: (content: string) => void;
|
|
39
|
+
/** Save spec content to storage */
|
|
40
|
+
save: () => void;
|
|
41
|
+
/** Validate spec content */
|
|
42
|
+
validate: () => SpecValidationResult;
|
|
43
|
+
/** Reset to template default */
|
|
44
|
+
reset: () => void;
|
|
45
|
+
/** Last saved timestamp */
|
|
46
|
+
lastSaved: string | null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Hook for managing spec content with persistence for a template.
|
|
51
|
+
* Uses localStorage for persistence in the sandbox environment.
|
|
52
|
+
*/
|
|
53
|
+
export function useSpecContent(templateId: TemplateId): UseSpecContentReturn {
|
|
54
|
+
const { template } = useTemplateRuntime();
|
|
55
|
+
const [content, setContentState] = useState<string>('');
|
|
56
|
+
const [savedContent, setSavedContent] = useState<string>('');
|
|
57
|
+
const [loading, setLoading] = useState(true);
|
|
58
|
+
const [validation, setValidation] = useState<SpecValidationResult | null>(
|
|
59
|
+
null
|
|
60
|
+
);
|
|
61
|
+
const [lastSaved, setLastSaved] = useState<string | null>(null);
|
|
62
|
+
|
|
63
|
+
// Load spec content from storage on mount
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
setLoading(true);
|
|
66
|
+
try {
|
|
67
|
+
const stored = localStorage.getItem(`${SPEC_STORAGE_KEY}-${templateId}`);
|
|
68
|
+
if (stored) {
|
|
69
|
+
const parsed = JSON.parse(stored) as {
|
|
70
|
+
content: string;
|
|
71
|
+
savedAt: string;
|
|
72
|
+
};
|
|
73
|
+
if (parsed.content) {
|
|
74
|
+
setContentState(parsed.content);
|
|
75
|
+
setSavedContent(parsed.content);
|
|
76
|
+
setLastSaved(parsed.savedAt);
|
|
77
|
+
} else {
|
|
78
|
+
// Invalid stored state, generate from template
|
|
79
|
+
const generated = generateSpecFromTemplate(template);
|
|
80
|
+
setContentState(generated);
|
|
81
|
+
setSavedContent(generated);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
// No stored state, generate from template
|
|
85
|
+
const generated = generateSpecFromTemplate(template);
|
|
86
|
+
setContentState(generated);
|
|
87
|
+
setSavedContent(generated);
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
// On error, generate from template
|
|
91
|
+
const generated = generateSpecFromTemplate(template);
|
|
92
|
+
setContentState(generated);
|
|
93
|
+
setSavedContent(generated);
|
|
94
|
+
}
|
|
95
|
+
setLoading(false);
|
|
96
|
+
}, [templateId]);
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Update spec content (in-memory only until save)
|
|
100
|
+
*/
|
|
101
|
+
const setContent = useCallback((newContent: string): void => {
|
|
102
|
+
setContentState(newContent);
|
|
103
|
+
// Clear validation when content changes
|
|
104
|
+
setValidation(null);
|
|
105
|
+
}, []);
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Save spec content to storage
|
|
109
|
+
*/
|
|
110
|
+
const save = useCallback((): void => {
|
|
111
|
+
try {
|
|
112
|
+
const savedAt = new Date().toISOString();
|
|
113
|
+
localStorage.setItem(
|
|
114
|
+
`${SPEC_STORAGE_KEY}-${templateId}`,
|
|
115
|
+
JSON.stringify({
|
|
116
|
+
content,
|
|
117
|
+
savedAt,
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
setSavedContent(content);
|
|
121
|
+
setLastSaved(savedAt);
|
|
122
|
+
} catch {
|
|
123
|
+
// Ignore storage errors
|
|
124
|
+
}
|
|
125
|
+
}, [content, templateId]);
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Validate spec content
|
|
129
|
+
* Performs basic syntax validation
|
|
130
|
+
*/
|
|
131
|
+
const validate = useCallback((): SpecValidationResult => {
|
|
132
|
+
const errors: SpecValidationResult['errors'] = [];
|
|
133
|
+
|
|
134
|
+
// Basic validation rules
|
|
135
|
+
const lines = content.split('\n');
|
|
136
|
+
|
|
137
|
+
// Check for contractSpec function call
|
|
138
|
+
if (!content.includes('contractSpec(')) {
|
|
139
|
+
errors.push({
|
|
140
|
+
line: 1,
|
|
141
|
+
message: 'Spec must contain a contractSpec() definition',
|
|
142
|
+
severity: 'error',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Check for required fields
|
|
147
|
+
if (!content.includes('goal:')) {
|
|
148
|
+
errors.push({
|
|
149
|
+
line: 1,
|
|
150
|
+
message: 'Spec should have a goal field',
|
|
151
|
+
severity: 'warning',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!content.includes('io:')) {
|
|
156
|
+
errors.push({
|
|
157
|
+
line: 1,
|
|
158
|
+
message: 'Spec should define io (input/output)',
|
|
159
|
+
severity: 'warning',
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Check for balanced braces
|
|
164
|
+
const openBraces = (content.match(/{/g) ?? []).length;
|
|
165
|
+
const closeBraces = (content.match(/}/g) ?? []).length;
|
|
166
|
+
if (openBraces !== closeBraces) {
|
|
167
|
+
errors.push({
|
|
168
|
+
line: lines.length,
|
|
169
|
+
message: `Unbalanced braces: ${openBraces} opening, ${closeBraces} closing`,
|
|
170
|
+
severity: 'error',
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check for balanced parentheses
|
|
175
|
+
const openParens = (content.match(/\(/g) ?? []).length;
|
|
176
|
+
const closeParens = (content.match(/\)/g) ?? []).length;
|
|
177
|
+
if (openParens !== closeParens) {
|
|
178
|
+
errors.push({
|
|
179
|
+
line: lines.length,
|
|
180
|
+
message: `Unbalanced parentheses: ${openParens} opening, ${closeParens} closing`,
|
|
181
|
+
severity: 'error',
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Check for unclosed strings (basic check)
|
|
186
|
+
lines.forEach((line, index) => {
|
|
187
|
+
const singleQuotes = (line.match(/'/g) ?? []).length;
|
|
188
|
+
const doubleQuotes = (line.match(/"/g) ?? []).length;
|
|
189
|
+
if (singleQuotes % 2 !== 0) {
|
|
190
|
+
errors.push({
|
|
191
|
+
line: index + 1,
|
|
192
|
+
message: 'Unclosed single quote',
|
|
193
|
+
severity: 'error',
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (doubleQuotes % 2 !== 0) {
|
|
197
|
+
errors.push({
|
|
198
|
+
line: index + 1,
|
|
199
|
+
message: 'Unclosed double quote',
|
|
200
|
+
severity: 'error',
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const result: SpecValidationResult = {
|
|
206
|
+
valid: errors.filter((e) => e.severity === 'error').length === 0,
|
|
207
|
+
errors,
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
setValidation(result);
|
|
211
|
+
return result;
|
|
212
|
+
}, [content]);
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Reset to template default
|
|
216
|
+
*/
|
|
217
|
+
const reset = useCallback((): void => {
|
|
218
|
+
const generated = generateSpecFromTemplate(template);
|
|
219
|
+
setContentState(generated);
|
|
220
|
+
setSavedContent(generated);
|
|
221
|
+
setValidation(null);
|
|
222
|
+
setLastSaved(null);
|
|
223
|
+
|
|
224
|
+
// Clear from storage
|
|
225
|
+
try {
|
|
226
|
+
localStorage.removeItem(`${SPEC_STORAGE_KEY}-${templateId}`);
|
|
227
|
+
} catch {
|
|
228
|
+
// Ignore storage errors
|
|
229
|
+
}
|
|
230
|
+
}, [templateId]);
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
content,
|
|
234
|
+
loading,
|
|
235
|
+
isDirty: content !== savedContent,
|
|
236
|
+
validation,
|
|
237
|
+
setContent,
|
|
238
|
+
save,
|
|
239
|
+
validate,
|
|
240
|
+
reset,
|
|
241
|
+
lastSaved,
|
|
242
|
+
};
|
|
243
|
+
}
|