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

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 +24 -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
@@ -7,58 +7,58 @@ import type { TemplateId } from '../lib/types';
7
7
  * Behavior event types
8
8
  */
9
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';
10
+ | 'template_view'
11
+ | 'mode_change'
12
+ | 'spec_edit'
13
+ | 'canvas_interaction'
14
+ | 'presentation_view'
15
+ | 'feature_usage'
16
+ | 'error'
17
+ | 'navigation';
18
18
 
19
19
  /**
20
20
  * Behavior event data
21
21
  */
22
22
  export interface BehaviorEvent {
23
- type: BehaviorEventType;
24
- timestamp: Date;
25
- templateId: TemplateId;
26
- metadata?: Record<string, unknown>;
23
+ type: BehaviorEventType;
24
+ timestamp: Date;
25
+ templateId: TemplateId;
26
+ metadata?: Record<string, unknown>;
27
27
  }
28
28
 
29
29
  /**
30
30
  * Behavior summary for a session
31
31
  */
32
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[];
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
41
  }
42
42
 
43
43
  /**
44
44
  * Hook return type
45
45
  */
46
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;
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
62
  }
63
63
 
64
64
  /**
@@ -70,16 +70,16 @@ const BEHAVIOR_STORAGE_KEY = 'contractspec-behavior-data';
70
70
  * All available features in the sandbox
71
71
  */
72
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',
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
83
  ];
84
84
 
85
85
  /**
@@ -87,241 +87,241 @@ const ALL_FEATURES = [
87
87
  * Provides insights into usage patterns and feature adoption.
88
88
  */
89
89
  export function useBehaviorTracking(
90
- templateId: TemplateId
90
+ templateId: TemplateId
91
91
  ): UseBehaviorTrackingReturn {
92
- const [events, setEvents] = useState<BehaviorEvent[]>([]);
93
- const sessionStartRef = useRef<Date>(new Date());
94
- const [eventCount, setEventCount] = useState(0);
92
+ const [events, setEvents] = useState<BehaviorEvent[]>([]);
93
+ const sessionStartRef = useRef<Date>(new Date());
94
+ const [eventCount, setEventCount] = useState(0);
95
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
- }, []);
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
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]);
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
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
- );
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
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
- );
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
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();
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
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);
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
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);
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
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
- }
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
216
 
217
- // Find unused features
218
- const unusedFeatures = ALL_FEATURES.filter((f) => !featuresUsed.has(f));
217
+ // Find unused features
218
+ const unusedFeatures = ALL_FEATURES.filter((f) => !featuresUsed.has(f));
219
219
 
220
- // Count errors
221
- const errorCount = events.filter((e) => e.type === 'error').length;
220
+ // Count errors
221
+ const errorCount = events.filter((e) => e.type === 'error').length;
222
222
 
223
- // Generate recommendations
224
- const recommendations = generateRecommendations(
225
- Array.from(featuresUsed),
226
- unusedFeatures,
227
- mostUsedModes,
228
- events.length
229
- );
223
+ // Generate recommendations
224
+ const recommendations = generateRecommendations(
225
+ Array.from(featuresUsed),
226
+ unusedFeatures,
227
+ mostUsedModes,
228
+ events.length
229
+ );
230
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]);
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
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
- }, []);
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
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
- );
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
264
  }
265
265
 
266
266
  /**
267
267
  * Generate recommendations based on behavior
268
268
  */
269
269
  function generateRecommendations(
270
- featuresUsed: string[],
271
- unusedFeatures: string[],
272
- mostUsedModes: { mode: string; count: number }[],
273
- totalEvents: number
270
+ featuresUsed: string[],
271
+ unusedFeatures: string[],
272
+ mostUsedModes: { mode: string; count: number }[],
273
+ totalEvents: number
274
274
  ): string[] {
275
- const recommendations: string[] = [];
275
+ const recommendations: string[] = [];
276
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
- }
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
283
 
284
- if (unusedFeatures.includes('markdown')) {
285
- recommendations.push(
286
- 'Use Markdown preview to see documentation for your specs'
287
- );
288
- }
284
+ if (unusedFeatures.includes('markdown')) {
285
+ recommendations.push(
286
+ 'Use Markdown preview to see documentation for your specs'
287
+ );
288
+ }
289
289
 
290
- if (unusedFeatures.includes('builder')) {
291
- recommendations.push(
292
- 'Explore the Visual Builder to design your UI components'
293
- );
294
- }
290
+ if (unusedFeatures.includes('builder')) {
291
+ recommendations.push(
292
+ 'Explore the Visual Builder to design your UI components'
293
+ );
294
+ }
295
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
- }
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
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
- }
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
311
 
312
- // Time-based recommendations
313
- if (totalEvents > 50) {
314
- recommendations.push(
315
- 'Great engagement! Consider saving your work regularly'
316
- );
317
- }
312
+ // Time-based recommendations
313
+ if (totalEvents > 50) {
314
+ recommendations.push(
315
+ 'Great engagement! Consider saving your work regularly'
316
+ );
317
+ }
318
318
 
319
- // Mode variety recommendations
320
- if (mostUsedModes.length === 1) {
321
- recommendations.push(
322
- 'Try different modes to explore all sandbox capabilities'
323
- );
324
- }
319
+ // Mode variety recommendations
320
+ if (mostUsedModes.length === 1) {
321
+ recommendations.push(
322
+ 'Try different modes to explore all sandbox capabilities'
323
+ );
324
+ }
325
325
 
326
- return recommendations;
326
+ return recommendations;
327
327
  }