@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,165 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect } from 'react';
|
|
4
|
+
import { Button, LoaderBlock } from '@contractspec/lib.design-system';
|
|
5
|
+
import { Badge } from '@contractspec/lib.ui-kit-web/ui/badge';
|
|
6
|
+
import type { TemplateId } from './lib/types';
|
|
7
|
+
import { useSpecContent } from './hooks/useSpecContent';
|
|
8
|
+
|
|
9
|
+
export interface SpecEditorProps {
|
|
10
|
+
projectId: string;
|
|
11
|
+
type?: 'CAPABILITY' | 'DATAVIEW' | 'WORKFLOW' | 'POLICY' | 'COMPONENT';
|
|
12
|
+
content: string;
|
|
13
|
+
onChange: (content: string) => void;
|
|
14
|
+
metadata?: Record<string, unknown>;
|
|
15
|
+
onSave?: () => void;
|
|
16
|
+
onValidate?: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SpecEditorPanelProps {
|
|
20
|
+
templateId: TemplateId;
|
|
21
|
+
/** SpecEditor component passed as a prop (for dynamic import compatibility) */
|
|
22
|
+
SpecEditor: React.ComponentType<SpecEditorProps>;
|
|
23
|
+
/** Callback for logging actions */
|
|
24
|
+
onLog?: (message: string) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Spec editor panel that wraps SpecEditor with persisted spec content.
|
|
29
|
+
* Uses useSpecContent hook to manage spec persistence and validation.
|
|
30
|
+
*/
|
|
31
|
+
export function SpecEditorPanel({
|
|
32
|
+
templateId,
|
|
33
|
+
SpecEditor,
|
|
34
|
+
onLog,
|
|
35
|
+
}: SpecEditorPanelProps) {
|
|
36
|
+
const {
|
|
37
|
+
content,
|
|
38
|
+
loading,
|
|
39
|
+
isDirty,
|
|
40
|
+
validation,
|
|
41
|
+
setContent,
|
|
42
|
+
save,
|
|
43
|
+
validate,
|
|
44
|
+
reset,
|
|
45
|
+
lastSaved,
|
|
46
|
+
} = useSpecContent(templateId);
|
|
47
|
+
|
|
48
|
+
// Log when spec is loaded
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (!loading && content) {
|
|
51
|
+
onLog?.(`Spec loaded for ${templateId}`);
|
|
52
|
+
}
|
|
53
|
+
}, [loading, content, templateId, onLog]);
|
|
54
|
+
|
|
55
|
+
const handleSave = useCallback(() => {
|
|
56
|
+
save();
|
|
57
|
+
onLog?.('Spec saved locally');
|
|
58
|
+
}, [save, onLog]);
|
|
59
|
+
|
|
60
|
+
const handleValidate = useCallback(() => {
|
|
61
|
+
const result = validate();
|
|
62
|
+
if (result.valid) {
|
|
63
|
+
onLog?.('Spec validation passed');
|
|
64
|
+
} else {
|
|
65
|
+
const errorCount = result.errors.filter(
|
|
66
|
+
(e) => e.severity === 'error'
|
|
67
|
+
).length;
|
|
68
|
+
const warnCount = result.errors.filter(
|
|
69
|
+
(e) => e.severity === 'warning'
|
|
70
|
+
).length;
|
|
71
|
+
onLog?.(`Spec validation: ${errorCount} errors, ${warnCount} warnings`);
|
|
72
|
+
}
|
|
73
|
+
}, [validate, onLog]);
|
|
74
|
+
|
|
75
|
+
const handleReset = useCallback(() => {
|
|
76
|
+
reset();
|
|
77
|
+
onLog?.('Spec reset to template defaults');
|
|
78
|
+
}, [reset, onLog]);
|
|
79
|
+
|
|
80
|
+
if (loading) {
|
|
81
|
+
return <LoaderBlock label="Loading spec..." />;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div className="space-y-4">
|
|
86
|
+
{/* Spec Toolbar */}
|
|
87
|
+
<div className="flex items-center justify-between">
|
|
88
|
+
<div className="flex items-center gap-2">
|
|
89
|
+
<Button variant="default" size="sm" onClick={handleSave}>
|
|
90
|
+
Save
|
|
91
|
+
</Button>
|
|
92
|
+
<Button variant="outline" size="sm" onClick={handleValidate}>
|
|
93
|
+
Validate
|
|
94
|
+
</Button>
|
|
95
|
+
{isDirty && (
|
|
96
|
+
<Badge
|
|
97
|
+
variant="secondary"
|
|
98
|
+
className="border-amber-500/30 bg-amber-500/20 text-amber-400"
|
|
99
|
+
>
|
|
100
|
+
Unsaved changes
|
|
101
|
+
</Badge>
|
|
102
|
+
)}
|
|
103
|
+
{validation && (
|
|
104
|
+
<Badge
|
|
105
|
+
variant={validation.valid ? 'default' : 'destructive'}
|
|
106
|
+
className={
|
|
107
|
+
validation.valid
|
|
108
|
+
? 'border-green-500/30 bg-green-500/20 text-green-400'
|
|
109
|
+
: ''
|
|
110
|
+
}
|
|
111
|
+
>
|
|
112
|
+
{validation.valid
|
|
113
|
+
? 'Valid'
|
|
114
|
+
: `${validation.errors.filter((e) => e.severity === 'error').length} errors`}
|
|
115
|
+
</Badge>
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
118
|
+
<div className="flex items-center gap-2">
|
|
119
|
+
{lastSaved && (
|
|
120
|
+
<span className="text-muted-foreground text-xs">
|
|
121
|
+
Last saved: {new Date(lastSaved).toLocaleTimeString()}
|
|
122
|
+
</span>
|
|
123
|
+
)}
|
|
124
|
+
<Button variant="ghost" size="sm" onPress={handleReset}>
|
|
125
|
+
Reset
|
|
126
|
+
</Button>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
{/* Validation Errors */}
|
|
131
|
+
{validation && validation.errors.length > 0 && (
|
|
132
|
+
<div className="rounded-lg border border-amber-500/50 bg-amber-500/10 p-3">
|
|
133
|
+
<p className="mb-2 text-xs font-semibold text-amber-400 uppercase">
|
|
134
|
+
Validation Issues
|
|
135
|
+
</p>
|
|
136
|
+
<ul className="space-y-1">
|
|
137
|
+
{validation.errors.map((error, index) => (
|
|
138
|
+
<li
|
|
139
|
+
key={`${error.line}-${error.message}-${index}`}
|
|
140
|
+
className={`text-xs ${
|
|
141
|
+
error.severity === 'error' ? 'text-red-400' : 'text-amber-400'
|
|
142
|
+
}`}
|
|
143
|
+
>
|
|
144
|
+
Line {error.line}: {error.message}
|
|
145
|
+
</li>
|
|
146
|
+
))}
|
|
147
|
+
</ul>
|
|
148
|
+
</div>
|
|
149
|
+
)}
|
|
150
|
+
|
|
151
|
+
{/* Editor */}
|
|
152
|
+
<div className="border-border bg-card rounded-2xl border p-4">
|
|
153
|
+
<SpecEditor
|
|
154
|
+
projectId="sandbox"
|
|
155
|
+
type="CAPABILITY"
|
|
156
|
+
content={content}
|
|
157
|
+
onChange={setContent}
|
|
158
|
+
metadata={{ template: templateId }}
|
|
159
|
+
onSave={handleSave}
|
|
160
|
+
onValidate={handleValidate}
|
|
161
|
+
/>
|
|
162
|
+
</div>
|
|
163
|
+
</div>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { LocalDataIndicator } from './LocalDataIndicator';
|
|
4
|
+
import {
|
|
5
|
+
SaveToStudioButton,
|
|
6
|
+
type SaveToStudioButtonProps,
|
|
7
|
+
} from './SaveToStudioButton';
|
|
8
|
+
|
|
9
|
+
export interface TemplateShellProps {
|
|
10
|
+
title: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
sidebar?: ReactNode;
|
|
13
|
+
actions?: ReactNode;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
showSaveAction?: boolean;
|
|
16
|
+
saveProps?: SaveToStudioButtonProps;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const TemplateShell = ({
|
|
20
|
+
title,
|
|
21
|
+
description,
|
|
22
|
+
sidebar,
|
|
23
|
+
actions,
|
|
24
|
+
showSaveAction = true,
|
|
25
|
+
saveProps,
|
|
26
|
+
children,
|
|
27
|
+
}: TemplateShellProps) => (
|
|
28
|
+
<div className="space-y-6">
|
|
29
|
+
<header className="border-border bg-card rounded-2xl border p-6 shadow-sm">
|
|
30
|
+
<div className="flex flex-wrap items-center justify-between gap-4">
|
|
31
|
+
<div>
|
|
32
|
+
<p className="text-muted-foreground text-sm font-semibold tracking-wide uppercase">
|
|
33
|
+
ContractSpec Templates
|
|
34
|
+
</p>
|
|
35
|
+
<h1 className="text-3xl font-bold">{title}</h1>
|
|
36
|
+
{description ? (
|
|
37
|
+
<p className="text-muted-foreground mt-2 max-w-2xl text-sm">
|
|
38
|
+
{description}
|
|
39
|
+
</p>
|
|
40
|
+
) : null}
|
|
41
|
+
</div>
|
|
42
|
+
<div className="flex flex-col items-end gap-2">
|
|
43
|
+
<LocalDataIndicator />
|
|
44
|
+
{showSaveAction ? <SaveToStudioButton {...saveProps} /> : null}
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
{actions ? <div className="mt-4">{actions}</div> : null}
|
|
48
|
+
</header>
|
|
49
|
+
|
|
50
|
+
<div
|
|
51
|
+
className={
|
|
52
|
+
sidebar ? 'grid gap-6 lg:grid-cols-[minmax(0,1fr)_320px]' : 'w-full'
|
|
53
|
+
}
|
|
54
|
+
>
|
|
55
|
+
<main className="space-y-4 p-2">{children}</main>
|
|
56
|
+
{sidebar ? (
|
|
57
|
+
<aside className="border-border bg-card rounded-2xl border p-4">
|
|
58
|
+
{sidebar}
|
|
59
|
+
</aside>
|
|
60
|
+
) : null}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import type { TemplateId } from '../lib/types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Behavior event types
|
|
8
|
+
*/
|
|
9
|
+
export type BehaviorEventType =
|
|
10
|
+
| 'template_view'
|
|
11
|
+
| 'mode_change'
|
|
12
|
+
| 'spec_edit'
|
|
13
|
+
| 'canvas_interaction'
|
|
14
|
+
| 'presentation_view'
|
|
15
|
+
| 'feature_usage'
|
|
16
|
+
| 'error'
|
|
17
|
+
| 'navigation';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Behavior event data
|
|
21
|
+
*/
|
|
22
|
+
export interface BehaviorEvent {
|
|
23
|
+
type: BehaviorEventType;
|
|
24
|
+
timestamp: Date;
|
|
25
|
+
templateId: TemplateId;
|
|
26
|
+
metadata?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Behavior summary for a session
|
|
31
|
+
*/
|
|
32
|
+
export interface BehaviorSummary {
|
|
33
|
+
totalEvents: number;
|
|
34
|
+
sessionDuration: number;
|
|
35
|
+
mostUsedTemplates: { templateId: TemplateId; count: number }[];
|
|
36
|
+
mostUsedModes: { mode: string; count: number }[];
|
|
37
|
+
featuresUsed: string[];
|
|
38
|
+
unusedFeatures: string[];
|
|
39
|
+
errorCount: number;
|
|
40
|
+
recommendations: string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Hook return type
|
|
45
|
+
*/
|
|
46
|
+
export interface UseBehaviorTrackingReturn {
|
|
47
|
+
/** Track a behavior event */
|
|
48
|
+
trackEvent: (
|
|
49
|
+
type: BehaviorEventType,
|
|
50
|
+
metadata?: Record<string, unknown>
|
|
51
|
+
) => void;
|
|
52
|
+
/** Get behavior summary */
|
|
53
|
+
getSummary: () => BehaviorSummary;
|
|
54
|
+
/** Get events for a specific type */
|
|
55
|
+
getEventsByType: (type: BehaviorEventType) => BehaviorEvent[];
|
|
56
|
+
/** Total event count */
|
|
57
|
+
eventCount: number;
|
|
58
|
+
/** Session start time */
|
|
59
|
+
sessionStart: Date;
|
|
60
|
+
/** Clear all tracked data */
|
|
61
|
+
clear: () => void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Storage key for behavior data
|
|
66
|
+
*/
|
|
67
|
+
const BEHAVIOR_STORAGE_KEY = 'contractspec-behavior-data';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* All available features in the sandbox
|
|
71
|
+
*/
|
|
72
|
+
const ALL_FEATURES = [
|
|
73
|
+
'playground',
|
|
74
|
+
'specs',
|
|
75
|
+
'builder',
|
|
76
|
+
'markdown',
|
|
77
|
+
'evolution',
|
|
78
|
+
'canvas_add',
|
|
79
|
+
'canvas_delete',
|
|
80
|
+
'spec_save',
|
|
81
|
+
'spec_validate',
|
|
82
|
+
'ai_suggestions',
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Hook for tracking user behavior in the sandbox.
|
|
87
|
+
* Provides insights into usage patterns and feature adoption.
|
|
88
|
+
*/
|
|
89
|
+
export function useBehaviorTracking(
|
|
90
|
+
templateId: TemplateId
|
|
91
|
+
): UseBehaviorTrackingReturn {
|
|
92
|
+
const [events, setEvents] = useState<BehaviorEvent[]>([]);
|
|
93
|
+
const sessionStartRef = useRef<Date>(new Date());
|
|
94
|
+
const [eventCount, setEventCount] = useState(0);
|
|
95
|
+
|
|
96
|
+
// Load persisted events on mount
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
try {
|
|
99
|
+
const stored = localStorage.getItem(BEHAVIOR_STORAGE_KEY);
|
|
100
|
+
if (stored) {
|
|
101
|
+
const data = JSON.parse(stored) as {
|
|
102
|
+
events: (Omit<BehaviorEvent, 'timestamp'> & { timestamp: string })[];
|
|
103
|
+
sessionStart: string;
|
|
104
|
+
};
|
|
105
|
+
setEvents(
|
|
106
|
+
data.events.map((e) => ({
|
|
107
|
+
...e,
|
|
108
|
+
timestamp: new Date(e.timestamp),
|
|
109
|
+
}))
|
|
110
|
+
);
|
|
111
|
+
sessionStartRef.current = new Date(data.sessionStart);
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
// Ignore storage errors
|
|
115
|
+
}
|
|
116
|
+
}, []);
|
|
117
|
+
|
|
118
|
+
// Persist events when they change
|
|
119
|
+
useEffect(() => {
|
|
120
|
+
if (events.length > 0) {
|
|
121
|
+
try {
|
|
122
|
+
localStorage.setItem(
|
|
123
|
+
BEHAVIOR_STORAGE_KEY,
|
|
124
|
+
JSON.stringify({
|
|
125
|
+
events: events.map((e) => ({
|
|
126
|
+
...e,
|
|
127
|
+
timestamp: e.timestamp.toISOString(),
|
|
128
|
+
})),
|
|
129
|
+
sessionStart: sessionStartRef.current.toISOString(),
|
|
130
|
+
})
|
|
131
|
+
);
|
|
132
|
+
} catch {
|
|
133
|
+
// Ignore storage errors
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}, [events]);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Track a behavior event
|
|
140
|
+
*/
|
|
141
|
+
const trackEvent = useCallback(
|
|
142
|
+
(type: BehaviorEventType, metadata?: Record<string, unknown>) => {
|
|
143
|
+
const event: BehaviorEvent = {
|
|
144
|
+
type,
|
|
145
|
+
timestamp: new Date(),
|
|
146
|
+
templateId,
|
|
147
|
+
metadata,
|
|
148
|
+
};
|
|
149
|
+
setEvents((prev) => [...prev, event]);
|
|
150
|
+
setEventCount((prev) => prev + 1);
|
|
151
|
+
},
|
|
152
|
+
[templateId]
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Get events by type
|
|
157
|
+
*/
|
|
158
|
+
const getEventsByType = useCallback(
|
|
159
|
+
(type: BehaviorEventType): BehaviorEvent[] => {
|
|
160
|
+
return events.filter((e) => e.type === type);
|
|
161
|
+
},
|
|
162
|
+
[events]
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get behavior summary
|
|
167
|
+
*/
|
|
168
|
+
const getSummary = useCallback((): BehaviorSummary => {
|
|
169
|
+
const now = new Date();
|
|
170
|
+
const sessionDuration = now.getTime() - sessionStartRef.current.getTime();
|
|
171
|
+
|
|
172
|
+
// Count templates
|
|
173
|
+
const templateCounts = new Map<TemplateId, number>();
|
|
174
|
+
for (const event of events) {
|
|
175
|
+
const count = templateCounts.get(event.templateId) ?? 0;
|
|
176
|
+
templateCounts.set(event.templateId, count + 1);
|
|
177
|
+
}
|
|
178
|
+
const mostUsedTemplates = Array.from(templateCounts.entries())
|
|
179
|
+
.map(([templateId, count]) => ({ templateId, count }))
|
|
180
|
+
.sort((a, b) => b.count - a.count)
|
|
181
|
+
.slice(0, 3);
|
|
182
|
+
|
|
183
|
+
// Count modes from mode_change events
|
|
184
|
+
const modeCounts = new Map<string, number>();
|
|
185
|
+
for (const event of events) {
|
|
186
|
+
if (event.type === 'mode_change' && event.metadata?.mode) {
|
|
187
|
+
const mode = event.metadata.mode as string;
|
|
188
|
+
const count = modeCounts.get(mode) ?? 0;
|
|
189
|
+
modeCounts.set(mode, count + 1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const mostUsedModes = Array.from(modeCounts.entries())
|
|
193
|
+
.map(([mode, count]) => ({ mode, count }))
|
|
194
|
+
.sort((a, b) => b.count - a.count);
|
|
195
|
+
|
|
196
|
+
// Track features used
|
|
197
|
+
const featuresUsed = new Set<string>();
|
|
198
|
+
for (const event of events) {
|
|
199
|
+
if (event.type === 'mode_change' && event.metadata?.mode) {
|
|
200
|
+
featuresUsed.add(event.metadata.mode as string);
|
|
201
|
+
}
|
|
202
|
+
if (event.type === 'feature_usage' && event.metadata?.feature) {
|
|
203
|
+
featuresUsed.add(event.metadata.feature as string);
|
|
204
|
+
}
|
|
205
|
+
if (event.type === 'canvas_interaction') {
|
|
206
|
+
const action = event.metadata?.action as string;
|
|
207
|
+
if (action === 'add') featuresUsed.add('canvas_add');
|
|
208
|
+
if (action === 'delete') featuresUsed.add('canvas_delete');
|
|
209
|
+
}
|
|
210
|
+
if (event.type === 'spec_edit') {
|
|
211
|
+
const action = event.metadata?.action as string;
|
|
212
|
+
if (action === 'save') featuresUsed.add('spec_save');
|
|
213
|
+
if (action === 'validate') featuresUsed.add('spec_validate');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Find unused features
|
|
218
|
+
const unusedFeatures = ALL_FEATURES.filter((f) => !featuresUsed.has(f));
|
|
219
|
+
|
|
220
|
+
// Count errors
|
|
221
|
+
const errorCount = events.filter((e) => e.type === 'error').length;
|
|
222
|
+
|
|
223
|
+
// Generate recommendations
|
|
224
|
+
const recommendations = generateRecommendations(
|
|
225
|
+
Array.from(featuresUsed),
|
|
226
|
+
unusedFeatures,
|
|
227
|
+
mostUsedModes,
|
|
228
|
+
events.length
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
totalEvents: events.length,
|
|
233
|
+
sessionDuration,
|
|
234
|
+
mostUsedTemplates,
|
|
235
|
+
mostUsedModes,
|
|
236
|
+
featuresUsed: Array.from(featuresUsed),
|
|
237
|
+
unusedFeatures,
|
|
238
|
+
errorCount,
|
|
239
|
+
recommendations,
|
|
240
|
+
};
|
|
241
|
+
}, [events]);
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Clear all tracking data
|
|
245
|
+
*/
|
|
246
|
+
const clear = useCallback(() => {
|
|
247
|
+
setEvents([]);
|
|
248
|
+
setEventCount(0);
|
|
249
|
+
sessionStartRef.current = new Date();
|
|
250
|
+
localStorage.removeItem(BEHAVIOR_STORAGE_KEY);
|
|
251
|
+
}, []);
|
|
252
|
+
|
|
253
|
+
return useMemo(
|
|
254
|
+
() => ({
|
|
255
|
+
trackEvent,
|
|
256
|
+
getSummary,
|
|
257
|
+
getEventsByType,
|
|
258
|
+
eventCount,
|
|
259
|
+
sessionStart: sessionStartRef.current,
|
|
260
|
+
clear,
|
|
261
|
+
}),
|
|
262
|
+
[trackEvent, getSummary, getEventsByType, eventCount, clear]
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Generate recommendations based on behavior
|
|
268
|
+
*/
|
|
269
|
+
function generateRecommendations(
|
|
270
|
+
featuresUsed: string[],
|
|
271
|
+
unusedFeatures: string[],
|
|
272
|
+
mostUsedModes: { mode: string; count: number }[],
|
|
273
|
+
totalEvents: number
|
|
274
|
+
): string[] {
|
|
275
|
+
const recommendations: string[] = [];
|
|
276
|
+
|
|
277
|
+
// Recommend unused features
|
|
278
|
+
if (unusedFeatures.includes('evolution')) {
|
|
279
|
+
recommendations.push(
|
|
280
|
+
'Try the AI Evolution mode to get automated improvement suggestions'
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (unusedFeatures.includes('markdown')) {
|
|
285
|
+
recommendations.push(
|
|
286
|
+
'Use Markdown preview to see documentation for your specs'
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (unusedFeatures.includes('builder')) {
|
|
291
|
+
recommendations.push(
|
|
292
|
+
'Explore the Visual Builder to design your UI components'
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (
|
|
297
|
+
!featuresUsed.includes('spec_validate') &&
|
|
298
|
+
featuresUsed.includes('specs')
|
|
299
|
+
) {
|
|
300
|
+
recommendations.push("Don't forget to validate your specs before saving");
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (
|
|
304
|
+
featuresUsed.includes('evolution') &&
|
|
305
|
+
!featuresUsed.includes('ai_suggestions')
|
|
306
|
+
) {
|
|
307
|
+
recommendations.push(
|
|
308
|
+
'Generate AI suggestions to get actionable improvement recommendations'
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Time-based recommendations
|
|
313
|
+
if (totalEvents > 50) {
|
|
314
|
+
recommendations.push(
|
|
315
|
+
'Great engagement! Consider saving your work regularly'
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Mode variety recommendations
|
|
320
|
+
if (mostUsedModes.length === 1) {
|
|
321
|
+
recommendations.push(
|
|
322
|
+
'Try different modes to explore all sandbox capabilities'
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return recommendations;
|
|
327
|
+
}
|