lula2 0.0.5 → 0.0.6

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 (108) hide show
  1. package/README.md +291 -8
  2. package/dist/_app/env.js +1 -0
  3. package/dist/_app/immutable/assets/0.DtiRW3lO.css +1 -0
  4. package/dist/_app/immutable/assets/DynamicControlEditor.BkVTzFZ-.css +1 -0
  5. package/dist/_app/immutable/chunks/7x_q-1ab.js +1 -0
  6. package/dist/_app/immutable/chunks/B19gt6-g.js +2 -0
  7. package/dist/_app/immutable/chunks/BR-0Dorr.js +1 -0
  8. package/dist/_app/immutable/chunks/B_3ksxz5.js +2 -0
  9. package/dist/_app/immutable/chunks/Bg_R1qWi.js +3 -0
  10. package/dist/_app/immutable/chunks/D3aNP_lg.js +1 -0
  11. package/dist/_app/immutable/chunks/D4Q_ObIy.js +1 -0
  12. package/dist/_app/immutable/chunks/DsnmJJEf.js +1 -0
  13. package/dist/_app/immutable/chunks/XY2j_owG.js +66 -0
  14. package/dist/_app/immutable/chunks/rzN25oDf.js +1 -0
  15. package/dist/_app/immutable/entry/app.r0uOd9qg.js +2 -0
  16. package/dist/_app/immutable/entry/start.DvoqR0rc.js +1 -0
  17. package/dist/_app/immutable/nodes/0.Ct6FAss_.js +1 -0
  18. package/dist/_app/immutable/nodes/1.DLoKuy8Q.js +1 -0
  19. package/dist/_app/immutable/nodes/2.IRkwSmiB.js +1 -0
  20. package/dist/_app/immutable/nodes/3.BrTg-ZHv.js +1 -0
  21. package/dist/_app/immutable/nodes/4.Blq-4WQS.js +9 -0
  22. package/dist/_app/version.json +1 -0
  23. package/dist/cli/commands/crawl.js +128 -0
  24. package/dist/cli/commands/ui.js +2769 -0
  25. package/dist/cli/commands/version.js +30 -0
  26. package/dist/cli/server/index.js +2713 -0
  27. package/dist/cli/server/server.js +2702 -0
  28. package/dist/cli/server/serverState.js +1199 -0
  29. package/dist/cli/server/spreadsheetRoutes.js +788 -0
  30. package/dist/cli/server/types.js +0 -0
  31. package/dist/cli/server/websocketServer.js +2625 -0
  32. package/dist/cli/utils/debug.js +24 -0
  33. package/dist/favicon.svg +1 -0
  34. package/dist/index.html +38 -0
  35. package/dist/index.js +2924 -37
  36. package/dist/lula.png +0 -0
  37. package/dist/lula2 +2 -0
  38. package/package.json +120 -72
  39. package/src/app.css +192 -0
  40. package/src/app.d.ts +13 -0
  41. package/src/app.html +13 -0
  42. package/src/lib/actions/fadeWhenScrollable.ts +39 -0
  43. package/src/lib/actions/modal.ts +230 -0
  44. package/src/lib/actions/tooltip.ts +82 -0
  45. package/src/lib/components/control-sets/ControlSetInfo.svelte +20 -0
  46. package/src/lib/components/control-sets/ControlSetSelector.svelte +46 -0
  47. package/src/lib/components/control-sets/index.ts +5 -0
  48. package/src/lib/components/controls/ControlDetailsPanel.svelte +235 -0
  49. package/src/lib/components/controls/ControlsList.svelte +608 -0
  50. package/src/lib/components/controls/DynamicControlEditor.svelte +298 -0
  51. package/src/lib/components/controls/MappingCard.svelte +105 -0
  52. package/src/lib/components/controls/MappingForm.svelte +188 -0
  53. package/src/lib/components/controls/index.ts +9 -0
  54. package/src/lib/components/controls/renderers/EditableFieldRenderer.svelte +103 -0
  55. package/src/lib/components/controls/renderers/FieldRenderer.svelte +49 -0
  56. package/src/lib/components/controls/renderers/index.ts +5 -0
  57. package/src/lib/components/controls/tabs/CustomFieldsTab.svelte +130 -0
  58. package/src/lib/components/controls/tabs/ImplementationTab.svelte +127 -0
  59. package/src/lib/components/controls/tabs/MappingsTab.svelte +182 -0
  60. package/src/lib/components/controls/tabs/OverviewTab.svelte +151 -0
  61. package/src/lib/components/controls/tabs/TimelineTab.svelte +41 -0
  62. package/src/lib/components/controls/tabs/index.ts +8 -0
  63. package/src/lib/components/controls/utils/ProcessedTextRenderer.svelte +63 -0
  64. package/src/lib/components/controls/utils/textProcessor.ts +164 -0
  65. package/src/lib/components/forms/DynamicControlForm.svelte +340 -0
  66. package/src/lib/components/forms/DynamicField.svelte +494 -0
  67. package/src/lib/components/forms/FormField.svelte +107 -0
  68. package/src/lib/components/forms/index.ts +6 -0
  69. package/src/lib/components/setup/ExistingControlSets.svelte +284 -0
  70. package/src/lib/components/setup/SpreadsheetImport.svelte +968 -0
  71. package/src/lib/components/setup/index.ts +5 -0
  72. package/src/lib/components/ui/Dropdown.svelte +107 -0
  73. package/src/lib/components/ui/EmptyState.svelte +80 -0
  74. package/src/lib/components/ui/FeatureToggle.svelte +50 -0
  75. package/src/lib/components/ui/SearchBar.svelte +73 -0
  76. package/src/lib/components/ui/StatusBadge.svelte +79 -0
  77. package/src/lib/components/ui/TabNavigation.svelte +48 -0
  78. package/src/lib/components/ui/Tooltip.svelte +120 -0
  79. package/src/lib/components/ui/index.ts +10 -0
  80. package/src/lib/components/version-control/DiffViewer.svelte +292 -0
  81. package/src/lib/components/version-control/TimelineItem.svelte +107 -0
  82. package/src/lib/components/version-control/YamlDiffViewer.svelte +428 -0
  83. package/src/lib/components/version-control/index.ts +6 -0
  84. package/src/lib/form-types.ts +57 -0
  85. package/src/lib/formatUtils.ts +17 -0
  86. package/src/lib/index.ts +5 -0
  87. package/src/lib/types.ts +180 -0
  88. package/src/lib/websocket.ts +359 -0
  89. package/src/routes/+layout.svelte +236 -0
  90. package/src/routes/+page.svelte +38 -0
  91. package/src/routes/control/[id]/+page.svelte +112 -0
  92. package/src/routes/setup/+page.svelte +241 -0
  93. package/src/stores/compliance.ts +95 -0
  94. package/src/styles/highlightjs.css +20 -0
  95. package/src/styles/modal.css +58 -0
  96. package/src/styles/tables.css +111 -0
  97. package/src/styles/tooltip.css +65 -0
  98. package/dist/controls/index.d.ts +0 -18
  99. package/dist/controls/index.d.ts.map +0 -1
  100. package/dist/controls/index.js +0 -18
  101. package/dist/crawl.d.ts +0 -62
  102. package/dist/crawl.d.ts.map +0 -1
  103. package/dist/crawl.js +0 -172
  104. package/dist/index.d.ts +0 -8
  105. package/dist/index.d.ts.map +0 -1
  106. package/src/controls/index.ts +0 -19
  107. package/src/crawl.ts +0 -227
  108. package/src/index.ts +0 -46
@@ -0,0 +1,340 @@
1
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
2
+ <!-- SPDX-FileCopyrightText: 2023-Present The Lula Authors -->
3
+
4
+ <script lang="ts">
5
+ import type { Control } from '$lib/types';
6
+ import type { ControlSchema, ValidationResult } from '$lib/form-types';
7
+ import DynamicField from './DynamicField.svelte';
8
+
9
+ interface Props {
10
+ control: Control;
11
+ schema: ControlSchema;
12
+ readonly?: boolean;
13
+ onValidation?: (result: ValidationResult) => void;
14
+ onChange?: () => void;
15
+ }
16
+
17
+ let { control = $bindable(), schema, readonly = false, onValidation, onChange }: Props = $props();
18
+
19
+ // Track validation errors for each field
20
+ let fieldErrors = $state<Record<string, string>>({});
21
+
22
+ // Group fields for better layout
23
+ const fieldGroups = $derived.by(() => {
24
+ const groups: { [key: string]: typeof schema.fields } = {};
25
+
26
+ schema.fields.forEach((field) => {
27
+ const group = field.group || 'general';
28
+ if (!groups[group]) {
29
+ groups[group] = [];
30
+ }
31
+ groups[group].push(field);
32
+ });
33
+
34
+ return groups;
35
+ });
36
+
37
+ function handleFieldChange(fieldId: string) {
38
+ // Run field-specific validation
39
+ validateField(fieldId);
40
+ onChange?.();
41
+ }
42
+
43
+ function validateField(fieldId: string) {
44
+ const field = schema.fields.find((f) => f.id === fieldId);
45
+ if (!field) return;
46
+
47
+ const value = control[fieldId];
48
+ let error = '';
49
+
50
+ // Required field validation
51
+ if (field.required && (!value || (typeof value === 'string' && value.trim() === ''))) {
52
+ error = `${field.label} is required`;
53
+ }
54
+
55
+ // Custom validation rules
56
+ if (field.validation && value) {
57
+ for (const rule of field.validation) {
58
+ if (rule.type === 'minLength' && typeof value === 'string' && value.length < rule.value) {
59
+ error = rule.message || `${field.label} must be at least ${rule.value} characters`;
60
+ break;
61
+ }
62
+ if (rule.type === 'maxLength' && typeof value === 'string' && value.length > rule.value) {
63
+ error = rule.message || `${field.label} must be no more than ${rule.value} characters`;
64
+ break;
65
+ }
66
+ if (
67
+ rule.type === 'pattern' &&
68
+ typeof value === 'string' &&
69
+ !new RegExp(rule.pattern!).test(value)
70
+ ) {
71
+ error = rule.message || `${field.label} format is invalid`;
72
+ break;
73
+ }
74
+ }
75
+ }
76
+
77
+ // Update field errors
78
+ if (error) {
79
+ fieldErrors[fieldId] = error;
80
+ } else {
81
+ delete fieldErrors[fieldId];
82
+ }
83
+
84
+ // Trigger validation callback
85
+ const hasErrors = Object.keys(fieldErrors).length > 0;
86
+ const validationResult: ValidationResult = {
87
+ valid: !hasErrors,
88
+ errors: Object.entries(fieldErrors).map(([field, message]) => ({ field, message })),
89
+ warnings: []
90
+ };
91
+
92
+ onValidation?.(validationResult);
93
+ }
94
+
95
+ // Only validate on explicit changes, not on control object changes
96
+ let isInitialized = $state(false);
97
+
98
+ $effect(() => {
99
+ if (control && !isInitialized) {
100
+ isInitialized = true;
101
+ // Initial validation without triggering callbacks
102
+ schema.fields.forEach((field) => {
103
+ if (control[field.id] !== undefined) {
104
+ const fieldId = field.id;
105
+ const fieldDef = schema.fields.find((f) => f.id === fieldId);
106
+ if (!fieldDef) return;
107
+
108
+ const value = control[fieldId];
109
+ let error = '';
110
+
111
+ // Required field validation
112
+ if (fieldDef.required && (!value || (typeof value === 'string' && value.trim() === ''))) {
113
+ error = `${fieldDef.label} is required`;
114
+ }
115
+
116
+ // Update field errors without triggering validation callback
117
+ if (error) {
118
+ fieldErrors[fieldId] = error;
119
+ } else {
120
+ delete fieldErrors[fieldId];
121
+ }
122
+ }
123
+ });
124
+ }
125
+ });
126
+ </script>
127
+
128
+ {#if readonly}
129
+ <!-- View Mode: Clean minimal layout -->
130
+ <div class="space-y-6">
131
+ {#each Object.entries(fieldGroups) as [groupName, fields]}
132
+ {#each [fields] as fieldList}
133
+ {@const importantFields = fieldList.filter((f) =>
134
+ ['id', 'title', 'priority', 'status'].includes(f.id)
135
+ )}
136
+ {@const contentFields = fieldList.filter(
137
+ (f) =>
138
+ !importantFields.includes(f) &&
139
+ control[f.id] !== undefined &&
140
+ control[f.id] !== null &&
141
+ control[f.id] !== ''
142
+ )}
143
+
144
+ {#if importantFields.length > 0 || contentFields.length > 0}
145
+ <div class="space-y-6">
146
+ <!-- Key information with natural layout -->
147
+ {#if importantFields.length > 0}
148
+ <div class="pb-4 border-b border-gray-200 dark:border-gray-700">
149
+ <div class="flex flex-wrap items-center gap-6">
150
+ {#each importantFields as field}
151
+ {@const value = control[field.id]}
152
+ {#if value !== undefined && value !== null && value !== ''}
153
+ <div class="flex items-center space-x-3">
154
+ <span class="text-sm font-medium text-gray-500 dark:text-gray-400">
155
+ {field.label}:
156
+ </span>
157
+ <span class="text-lg font-semibold text-gray-900 dark:text-white">
158
+ {#if field.type === 'boolean'}
159
+ <span
160
+ class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {value
161
+ ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
162
+ : 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200'}"
163
+ >
164
+ {value ? 'Yes' : 'No'}
165
+ </span>
166
+ {:else if field.id === 'family'}
167
+ <span
168
+ class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 uppercase"
169
+ >
170
+ {value}
171
+ </span>
172
+ {:else}
173
+ {value}
174
+ {/if}
175
+ </span>
176
+ </div>
177
+ {/if}
178
+ {/each}
179
+ </div>
180
+ </div>
181
+ {/if}
182
+
183
+ <!-- Content sections -->
184
+ {#each contentFields as field}
185
+ {@const value = control[field.id]}
186
+
187
+ {#if field.type === 'textarea'}
188
+ <!-- Long text content -->
189
+ <div class="space-y-3">
190
+ <h3 class="text-base font-semibold text-gray-900 dark:text-white">
191
+ {field.label}
192
+ </h3>
193
+ <div
194
+ class="text-gray-700 dark:text-gray-300 leading-relaxed whitespace-pre-wrap bg-gray-50 dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700"
195
+ >
196
+ {value}
197
+ </div>
198
+ </div>
199
+ {:else if field.type === 'string-array' && Array.isArray(value) && value.length > 0}
200
+ <!-- Simple list -->
201
+ <div class="space-y-3">
202
+ <h3 class="text-base font-semibold text-gray-900 dark:text-white">
203
+ {field.label}
204
+ <span class="ml-2 text-sm font-normal text-gray-500 dark:text-gray-400">
205
+ ({value.length})
206
+ </span>
207
+ </h3>
208
+ <div class="space-y-2">
209
+ {#each value as item}
210
+ <div class="flex items-start space-x-3 py-2">
211
+ <div class="flex-shrink-0 w-1.5 h-1.5 bg-blue-600 rounded-full mt-2"></div>
212
+ <div class="text-gray-900 dark:text-white leading-relaxed">
213
+ {item}
214
+ </div>
215
+ </div>
216
+ {/each}
217
+ </div>
218
+ </div>
219
+ {:else if field.type === 'object-array' && Array.isArray(value) && value.length > 0}
220
+ <!-- Object list -->
221
+ <div class="space-y-3">
222
+ <h3 class="text-base font-semibold text-gray-900 dark:text-white">
223
+ {field.label}
224
+ <span class="ml-2 text-sm font-normal text-gray-500 dark:text-gray-400">
225
+ ({value.length})
226
+ </span>
227
+ </h3>
228
+ <div class="space-y-3">
229
+ {#each value as item, index}
230
+ <div
231
+ class="bg-gray-50 dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700"
232
+ >
233
+ {#if field.arraySchema}
234
+ <dl class="space-y-2">
235
+ {#each Object.entries(field.arraySchema) as [key, schema]}
236
+ {@const schemaObj = schema as any}
237
+ {#if item[key]}
238
+ <div class="flex justify-between">
239
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
240
+ {schemaObj.label || key}:
241
+ </dt>
242
+ <dd class="text-sm text-gray-900 dark:text-white">
243
+ {item[key]}
244
+ </dd>
245
+ </div>
246
+ {/if}
247
+ {/each}
248
+ </dl>
249
+ {/if}
250
+ </div>
251
+ {/each}
252
+ </div>
253
+ </div>
254
+ {:else}
255
+ <!-- Simple field -->
256
+ <div class="flex justify-between py-2">
257
+ <dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
258
+ {field.label}:
259
+ </dt>
260
+ <dd class="text-sm text-gray-900 dark:text-white font-medium">
261
+ {#if field.type === 'boolean'}
262
+ <span
263
+ class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {value
264
+ ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
265
+ : 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200'}"
266
+ >
267
+ {value ? 'Yes' : 'No'}
268
+ </span>
269
+ {:else}
270
+ {value}
271
+ {/if}
272
+ </dd>
273
+ </div>
274
+ {/if}
275
+ {/each}
276
+ </div>
277
+ {/if}
278
+ {/each}
279
+ {/each}
280
+ </div>
281
+ {:else}
282
+ <!-- Edit Mode: Enhanced form layout -->
283
+ <div class="space-y-10">
284
+ {#each Object.entries(fieldGroups) as [groupName, fields]}
285
+ <section
286
+ class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-xl shadow-sm overflow-hidden"
287
+ >
288
+ {#if groupName !== 'general'}
289
+ <!-- Enhanced group header -->
290
+ <header
291
+ class="bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 px-8 py-6 border-b border-gray-200 dark:border-gray-600"
292
+ >
293
+ <h3 class="text-xl font-bold text-gray-900 dark:text-white tracking-tight">
294
+ {groupName.replace(/([A-Z])/g, ' $1').trim()}
295
+ </h3>
296
+ </header>
297
+ {/if}
298
+
299
+ <!-- Enhanced form content -->
300
+ <div class="p-8">
301
+ <!-- Improved grid layout for simple fields -->
302
+ <div class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-8 mb-10">
303
+ {#each fields as field}
304
+ {#if !['textarea', 'string-array', 'object-array'].includes(field.type)}
305
+ <div class="space-y-2">
306
+ <DynamicField
307
+ {field}
308
+ bind:value={control[field.id]}
309
+ {readonly}
310
+ error={fieldErrors[field.id]}
311
+ onChange={() => handleFieldChange(field.id)}
312
+ />
313
+ </div>
314
+ {/if}
315
+ {/each}
316
+ </div>
317
+
318
+ <!-- Enhanced full-width fields with better spacing -->
319
+ <div class="space-y-10">
320
+ {#each fields as field}
321
+ {#if ['textarea', 'string-array', 'object-array'].includes(field.type)}
322
+ <div
323
+ class="bg-gray-50 dark:bg-gray-800 rounded-xl p-6 border border-gray-200 dark:border-gray-700"
324
+ >
325
+ <DynamicField
326
+ {field}
327
+ bind:value={control[field.id]}
328
+ {readonly}
329
+ error={fieldErrors[field.id]}
330
+ onChange={() => handleFieldChange(field.id)}
331
+ />
332
+ </div>
333
+ {/if}
334
+ {/each}
335
+ </div>
336
+ </div>
337
+ </section>
338
+ {/each}
339
+ </div>
340
+ {/if}