@vc-shell/create-vc-app 1.1.99-alpha.0 → 1.1.99-alpha.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 (34) hide show
  1. package/README.md +41 -5
  2. package/dist/cli/argv.d.ts.map +1 -1
  3. package/dist/cli/help.d.ts.map +1 -1
  4. package/dist/cli/types.d.ts +5 -0
  5. package/dist/cli/types.d.ts.map +1 -1
  6. package/dist/commands/generate-blade.d.ts +5 -0
  7. package/dist/commands/generate-blade.d.ts.map +1 -1
  8. package/dist/index.js +940 -789
  9. package/dist/templates/base/_package.json +5 -5
  10. package/dist/templates/blades/grid/blade.vue +329 -340
  11. package/dist/templates/composables/details-composable.ts +2 -0
  12. package/dist/templates/composables/grid-composable.ts +3 -1
  13. package/dist/utils/naming.d.ts +1 -0
  14. package/dist/utils/naming.d.ts.map +1 -1
  15. package/dist/utils/register-module.d.ts +4 -0
  16. package/dist/utils/register-module.d.ts.map +1 -1
  17. package/dist/utils/templates.d.ts +10 -0
  18. package/dist/utils/templates.d.ts.map +1 -0
  19. package/dist/workflows/create-app.d.ts.map +1 -1
  20. package/package.json +3 -3
  21. package/dist/templates/base/ai-guides/.cursorrules-vc-shell +0 -529
  22. package/dist/templates/base/ai-guides/README.md +0 -360
  23. package/dist/templates/base/ai-guides/guides/AI_GUIDE.md +0 -195
  24. package/dist/templates/base/ai-guides/guides/blade-patterns.md +0 -384
  25. package/dist/templates/base/ai-guides/guides/complete-workflow.md +0 -781
  26. package/dist/templates/base/ai-guides/guides/composables-reference.md +0 -338
  27. package/dist/templates/base/ai-guides/guides/troubleshooting.md +0 -529
  28. package/dist/templates/base/ai-guides/guides/ui-components-reference.md +0 -903
  29. package/dist/templates/base/ai-guides/prompts/adapt-existing-module.md +0 -1026
  30. package/dist/templates/base/ai-guides/prompts/advanced-scenarios.md +0 -852
  31. package/dist/templates/base/ai-guides/prompts/api-client-generation.md +0 -877
  32. package/dist/templates/base/ai-guides/prompts/cli-usage.md +0 -640
  33. package/dist/templates/base/ai-guides/prompts/quick-start-scenarios.md +0 -773
  34. package/dist/templates/base/ai-guides/prompts/simple-modifications.md +0 -987
@@ -1,852 +0,0 @@
1
- # Advanced Scenarios
2
-
3
- Complex customization patterns and advanced use cases for VC Shell applications.
4
-
5
- ## When to Use This
6
-
7
- Use these prompts for:
8
- - Complex forms with dynamic fields
9
- - Master-detail relationships
10
- - Multi-step wizards
11
- - Advanced validation
12
- - File uploads with processing
13
- - Real-time updates
14
- - Complex business logic
15
-
16
- ---
17
-
18
- ## Scenario 1: Dynamic Form Fields
19
-
20
- **Use Case:** Form fields change based on other field values
21
-
22
- **Prompt for AI:**
23
- ```
24
- Create a product details blade where form fields change dynamically.
25
-
26
- Requirements:
27
- - Product type field (select: Physical, Digital, Service)
28
- - When "Physical" selected, show:
29
- - Weight (number)
30
- - Dimensions (length x width x height)
31
- - Shipping required (checkbox)
32
- - When "Digital" selected, show:
33
- - Download URL (text)
34
- - File size (number)
35
- - License key (text, auto-generated)
36
- - When "Service" selected, show:
37
- - Duration (number, hours)
38
- - Service location (select: On-site, Remote, Hybrid)
39
- - Required skills (multivalue)
40
-
41
- All conditional fields should appear/disappear smoothly.
42
- Use computed properties to control visibility.
43
- ```
44
-
45
- **Expected Implementation:**
46
- ```typescript
47
- const productType = ref<'physical' | 'digital' | 'service'>('physical');
48
-
49
- const showPhysicalFields = computed(() => productType.value === 'physical');
50
- const showDigitalFields = computed(() => productType.value === 'digital');
51
- const showServiceFields = computed(() => productType.value === 'service');
52
- ```
53
-
54
- ```vue
55
- <Field name="productType">
56
- <VcSelect
57
- v-model="productType"
58
- :options="['Physical', 'Digital', 'Service']"
59
- />
60
- </Field>
61
-
62
- <!-- Physical fields -->
63
- <template v-if="showPhysicalFields">
64
- <Field name="weight">
65
- <VcInput v-model="item.weight" type="number" />
66
- </Field>
67
- <!-- ... more physical fields -->
68
- </template>
69
-
70
- <!-- Digital fields -->
71
- <template v-if="showDigitalFields">
72
- <Field name="downloadUrl">
73
- <VcInput v-model="item.downloadUrl" />
74
- </Field>
75
- <!-- ... more digital fields -->
76
- </template>
77
- ```
78
-
79
- ---
80
-
81
- ## Scenario 2: Master-Detail Relationship
82
-
83
- **Use Case:** Edit item with related items (e.g., Order with Order Items)
84
-
85
- **Prompt for AI:**
86
- ```
87
- Create an order management system with master-detail relationship.
88
-
89
- Master (Order):
90
- - Order number
91
- - Customer
92
- - Order date
93
- - Status
94
- - Notes
95
-
96
- Detail (Order Items) - editable table:
97
- - Product (select from products list)
98
- - Quantity (number)
99
- - Unit price (currency, auto-filled from product)
100
- - Discount (percentage)
101
- - Total (calculated: quantity × price × (1 - discount))
102
- - Actions: Remove item
103
-
104
- Features:
105
- - Add item button (opens product selector)
106
- - Remove item button (with confirmation)
107
- - Subtotal (sum of all item totals)
108
- - Tax (10% of subtotal)
109
- - Shipping cost (manual entry)
110
- - Grand total (subtotal + tax + shipping)
111
- - All totals auto-calculate on any change
112
- - Validate: at least one item required
113
- - Validate: quantity > 0
114
- ```
115
-
116
- **Expected Implementation:**
117
- ```typescript
118
- // Order items array
119
- const items = ref<OrderItem[]>([]);
120
-
121
- // Calculated totals
122
- const subtotal = computed(() =>
123
- items.value.reduce((sum, item) => {
124
- const discount = item.discount || 0;
125
- return sum + (item.quantity * item.unitPrice * (1 - discount / 100));
126
- }, 0)
127
- );
128
-
129
- const tax = computed(() => subtotal.value * 0.1);
130
-
131
- const grandTotal = computed(() =>
132
- subtotal.value + tax.value + (order.value.shipping || 0)
133
- );
134
-
135
- // Add item
136
- const addItem = (product: Product) => {
137
- items.value.push({
138
- productId: product.id,
139
- productName: product.name,
140
- quantity: 1,
141
- unitPrice: product.price,
142
- discount: 0
143
- });
144
- };
145
-
146
- // Remove item
147
- const removeItem = (index: number) => {
148
- items.value.splice(index, 1);
149
- };
150
-
151
- // Watch for changes and update order
152
- watch([subtotal, tax, grandTotal], () => {
153
- order.value.subtotal = subtotal.value;
154
- order.value.tax = tax.value;
155
- order.value.total = grandTotal.value;
156
- });
157
- ```
158
-
159
- ---
160
-
161
- ## Scenario 3: Multi-Step Wizard
162
-
163
- **Use Case:** Complex form split into steps
164
-
165
- **Prompt for AI:**
166
- ```
167
- Create a product onboarding wizard with 4 steps.
168
-
169
- Step 1: Basic Info
170
- - Product name (text, required)
171
- - Category (select, required)
172
- - Brand (text)
173
-
174
- Step 2: Pricing
175
- - Cost price (currency, required)
176
- - Selling price (currency, required, must be > cost price)
177
- - Compare at price (currency, optional)
178
- - Margin (calculated, read-only)
179
-
180
- Step 3: Inventory
181
- - SKU (text, required, unique)
182
- - Quantity (number, required, min 0)
183
- - Track inventory (checkbox)
184
- - Low stock alert (number, shown only if track inventory)
185
-
186
- Step 4: Media
187
- - Images (gallery, drag to reorder)
188
- - Videos (file upload, mp4)
189
- - Documents (file upload, pdf)
190
-
191
- Navigation:
192
- - Next button (disabled if current step invalid)
193
- - Previous button (enabled except on step 1)
194
- - Save draft button (available on all steps)
195
- - Finish button (only on step 4, saves and closes)
196
-
197
- Progress indicator at top showing: Step 1 of 4, Step 2 of 4, etc.
198
- Validate each step before allowing next.
199
- Show step completion status (completed, current, upcoming).
200
- ```
201
-
202
- **Expected Implementation:**
203
- ```typescript
204
- const currentStep = ref(1);
205
- const totalSteps = 4;
206
-
207
- const steps = [
208
- { id: 1, title: 'Basic Info', completed: false },
209
- { id: 2, title: 'Pricing', completed: false },
210
- { id: 3, title: 'Inventory', completed: false },
211
- { id: 4, title: 'Media', completed: false }
212
- ];
213
-
214
- const canGoNext = computed(() => {
215
- switch(currentStep.value) {
216
- case 1:
217
- return !!item.value.name && !!item.value.category;
218
- case 2:
219
- return item.value.sellingPrice > item.value.costPrice;
220
- case 3:
221
- return !!item.value.sku && item.value.quantity >= 0;
222
- default:
223
- return true;
224
- }
225
- });
226
-
227
- const nextStep = () => {
228
- if (canGoNext.value && currentStep.value < totalSteps) {
229
- steps[currentStep.value - 1].completed = true;
230
- currentStep.value++;
231
- }
232
- };
233
-
234
- const previousStep = () => {
235
- if (currentStep.value > 1) {
236
- currentStep.value--;
237
- }
238
- };
239
-
240
- const saveDraft = async () => {
241
- item.value.status = 'draft';
242
- await api.save(item.value);
243
- notification.success('Draft saved');
244
- };
245
-
246
- const finish = async () => {
247
- item.value.status = 'active';
248
- await api.save(item.value);
249
- notification.success('Product created');
250
- closeBlade();
251
- };
252
- ```
253
-
254
- ---
255
-
256
- ## Scenario 4: Advanced Validation
257
-
258
- **Use Case:** Complex validation rules and cross-field validation
259
-
260
- **Prompt for AI:**
261
- ```
262
- Create a form with advanced validation rules.
263
-
264
- Fields:
265
- - Email (required, valid email format)
266
- - Password (required, min 8 chars, must have uppercase, lowercase, number, special char)
267
- - Confirm password (must match password)
268
- - Age (required, must be 18-100)
269
- - Phone (required, format: (XXX) XXX-XXXX)
270
- - Website (optional, valid URL if provided)
271
- - Start date (required)
272
- - End date (required, must be after start date)
273
- - Price (required, min 0, max 999999.99)
274
- - Quantity (required, integer, min 1)
275
-
276
- Custom validators:
277
- - Email must not be already registered (check with API)
278
- - Username must be unique (debounced check)
279
- - Start and end dates cannot be on weekends
280
-
281
- Show errors:
282
- - Inline below field
283
- - Summary at top of form
284
- - Highlight invalid fields in red
285
-
286
- Disable save button while any validation errors exist.
287
- ```
288
-
289
- **Expected Implementation:**
290
- ```typescript
291
- import { useForm, defineRule } from 'vee-validate';
292
-
293
- // Custom rule: password strength
294
- defineRule('strongPassword', (value: string) => {
295
- if (!value) return 'Password is required';
296
- if (value.length < 8) return 'At least 8 characters';
297
- if (!/[A-Z]/.test(value)) return 'At least one uppercase';
298
- if (!/[a-z]/.test(value)) return 'At least one lowercase';
299
- if (!/[0-9]/.test(value)) return 'At least one number';
300
- if (!/[^A-Za-z0-9]/.test(value)) return 'At least one special character';
301
- return true;
302
- });
303
-
304
- // Custom rule: date after
305
- defineRule('afterDate', (value: string, [target]: string[]) => {
306
- const valueDate = new Date(value);
307
- const targetDate = new Date(target);
308
- return valueDate > targetDate || 'Must be after start date';
309
- });
310
-
311
- // Custom rule: async unique check
312
- defineRule('uniqueEmail', async (value: string) => {
313
- const exists = await api.checkEmailExists(value);
314
- return !exists || 'Email already registered';
315
- });
316
-
317
- const validationSchema = {
318
- email: { required: true, email: true, uniqueEmail: true },
319
- password: { required: true, strongPassword: true },
320
- confirmPassword: { required: true, confirmed: '@password' },
321
- age: { required: true, min_value: 18, max_value: 100 },
322
- phone: { required: true, regex: /^\(\d{3}\) \d{3}-\d{4}$/ },
323
- website: { url: true },
324
- startDate: { required: true },
325
- endDate: { required: true, afterDate: '@startDate' },
326
- price: { required: true, min_value: 0, max_value: 999999.99 },
327
- quantity: { required: true, integer: true, min_value: 1 }
328
- };
329
-
330
- const { validate, errors, isValidating } = useForm({ validationSchema });
331
-
332
- const canSave = computed(() =>
333
- !isValidating.value && Object.keys(errors.value).length === 0
334
- );
335
- ```
336
-
337
- ---
338
-
339
- ## Scenario 5: File Upload with Preview
340
-
341
- **Use Case:** Upload multiple files with preview and processing
342
-
343
- **Prompt for AI:**
344
- ```
345
- Create a product gallery manager with advanced file upload.
346
-
347
- Features:
348
- - Drag & drop files
349
- - Multiple file selection
350
- - Image preview (thumbnails)
351
- - File type validation (jpg, png, webp only)
352
- - File size limit (max 5MB per file)
353
- - Image dimensions validation (min 800x600)
354
- - Upload progress bar per file
355
- - Cancel upload button
356
- - Remove uploaded image button
357
- - Reorder images (drag & drop)
358
- - Set primary image (star icon)
359
- - Edit image (crop, rotate, resize)
360
- - Bulk actions (select multiple, delete selected)
361
- - Total storage used indicator
362
-
363
- Show during upload:
364
- - Filename
365
- - File size
366
- - Progress percentage
367
- - Upload speed
368
- - Estimated time remaining
369
-
370
- After upload:
371
- - Thumbnail
372
- - Filename
373
- - File size
374
- - Dimensions
375
- - Actions (edit, delete, set as primary)
376
- ```
377
-
378
- **Expected Implementation:**
379
- ```typescript
380
- interface UploadFile {
381
- id: string;
382
- file: File;
383
- preview: string;
384
- progress: number;
385
- status: 'pending' | 'uploading' | 'completed' | 'error';
386
- error?: string;
387
- }
388
-
389
- const uploads = ref<UploadFile[]>([]);
390
- const existingImages = ref<Image[]>([]);
391
-
392
- // Handle file selection
393
- const handleFileSelect = async (files: FileList) => {
394
- for (const file of Array.from(files)) {
395
- // Validate
396
- if (!validateFile(file)) continue;
397
-
398
- // Create preview
399
- const preview = await createPreview(file);
400
-
401
- // Add to uploads
402
- uploads.value.push({
403
- id: crypto.randomUUID(),
404
- file,
405
- preview,
406
- progress: 0,
407
- status: 'pending'
408
- });
409
- }
410
-
411
- // Start uploads
412
- processUploads();
413
- };
414
-
415
- // Validate file
416
- const validateFile = (file: File): boolean => {
417
- // Type check
418
- if (!['image/jpeg', 'image/png', 'image/webp'].includes(file.type)) {
419
- notification.error(`Invalid file type: ${file.name}`);
420
- return false;
421
- }
422
-
423
- // Size check
424
- if (file.size > 5 * 1024 * 1024) {
425
- notification.error(`File too large: ${file.name} (max 5MB)`);
426
- return false;
427
- }
428
-
429
- return true;
430
- };
431
-
432
- // Create preview
433
- const createPreview = (file: File): Promise<string> => {
434
- return new Promise((resolve) => {
435
- const reader = new FileReader();
436
- reader.onload = (e) => resolve(e.target?.result as string);
437
- reader.readAsDataURL(file);
438
- });
439
- };
440
-
441
- // Upload files
442
- const processUploads = async () => {
443
- for (const upload of uploads.value) {
444
- if (upload.status !== 'pending') continue;
445
-
446
- upload.status = 'uploading';
447
-
448
- try {
449
- const formData = new FormData();
450
- formData.append('file', upload.file);
451
-
452
- const result = await api.uploadImage(formData, {
453
- onUploadProgress: (event) => {
454
- upload.progress = Math.round((event.loaded / event.total) * 100);
455
- }
456
- });
457
-
458
- upload.status = 'completed';
459
- existingImages.value.push(result.data);
460
-
461
- } catch (error) {
462
- upload.status = 'error';
463
- upload.error = error.message;
464
- }
465
- }
466
- };
467
-
468
- // Reorder images
469
- const reorderImages = (fromIndex: number, toIndex: number) => {
470
- const item = existingImages.value.splice(fromIndex, 1)[0];
471
- existingImages.value.splice(toIndex, 0, item);
472
- };
473
-
474
- // Set primary image
475
- const setPrimary = (imageId: string) => {
476
- existingImages.value.forEach(img => {
477
- img.isPrimary = img.id === imageId;
478
- });
479
- };
480
- ```
481
-
482
- ---
483
-
484
- ## Scenario 6: Real-Time Updates
485
-
486
- **Use Case:** Display real-time data updates using WebSocket or polling
487
-
488
- **Prompt for AI:**
489
- ```
490
- Create an order monitoring dashboard with real-time updates.
491
-
492
- Display:
493
- - Total orders today (updates every second)
494
- - Recent orders table (updates when new order arrives)
495
- - Order status chart (updates when status changes)
496
- - Active users count (live)
497
- - Revenue today (live)
498
-
499
- Implementation:
500
- - Use WebSocket for real-time updates
501
- - Fallback to polling if WebSocket unavailable
502
- - Show connection status indicator
503
- - Auto-reconnect on disconnect
504
- - Smooth animations for value changes
505
- - Flash highlight when data updates
506
- - Sound notification for new orders (optional)
507
- ```
508
-
509
- **Expected Implementation:**
510
- ```typescript
511
- import { ref, onMounted, onBeforeUnmount } from 'vue';
512
-
513
- let websocket: WebSocket | null = null;
514
- let reconnectTimeout: number;
515
-
516
- const isConnected = ref(false);
517
- const stats = ref({
518
- ordersToday: 0,
519
- activeUsers: 0,
520
- revenueToday: 0
521
- });
522
-
523
- const recentOrders = ref<Order[]>([]);
524
-
525
- // Connect to WebSocket
526
- const connect = () => {
527
- websocket = new WebSocket('wss://api.example.com/ws');
528
-
529
- websocket.onopen = () => {
530
- isConnected.value = true;
531
- console.log('WebSocket connected');
532
- };
533
-
534
- websocket.onmessage = (event) => {
535
- const data = JSON.parse(event.data);
536
- handleUpdate(data);
537
- };
538
-
539
- websocket.onerror = (error) => {
540
- console.error('WebSocket error:', error);
541
- };
542
-
543
- websocket.onclose = () => {
544
- isConnected.value = false;
545
- console.log('WebSocket disconnected');
546
-
547
- // Auto-reconnect after 5 seconds
548
- reconnectTimeout = setTimeout(connect, 5000);
549
- };
550
- };
551
-
552
- // Handle incoming updates
553
- const handleUpdate = (data: any) => {
554
- switch (data.type) {
555
- case 'stats':
556
- // Animate value change
557
- animateValue(stats.value, 'ordersToday', data.ordersToday);
558
- animateValue(stats.value, 'activeUsers', data.activeUsers);
559
- animateValue(stats.value, 'revenueToday', data.revenueToday);
560
- break;
561
-
562
- case 'newOrder':
563
- // Add to recent orders
564
- recentOrders.value.unshift(data.order);
565
- if (recentOrders.value.length > 10) {
566
- recentOrders.value.pop();
567
- }
568
-
569
- // Flash highlight
570
- flashHighlight(data.order.id);
571
-
572
- // Play sound
573
- playNotificationSound();
574
- break;
575
-
576
- case 'statusChange':
577
- // Update order status
578
- const order = recentOrders.value.find(o => o.id === data.orderId);
579
- if (order) {
580
- order.status = data.newStatus;
581
- flashHighlight(data.orderId);
582
- }
583
- break;
584
- }
585
- };
586
-
587
- // Animate value change
588
- const animateValue = (obj: any, key: string, target: number) => {
589
- const start = obj[key];
590
- const duration = 1000; // 1 second
591
- const startTime = Date.now();
592
-
593
- const animate = () => {
594
- const elapsed = Date.now() - startTime;
595
- const progress = Math.min(elapsed / duration, 1);
596
-
597
- obj[key] = Math.round(start + (target - start) * progress);
598
-
599
- if (progress < 1) {
600
- requestAnimationFrame(animate);
601
- }
602
- };
603
-
604
- animate();
605
- };
606
-
607
- // Fallback to polling
608
- const startPolling = () => {
609
- setInterval(async () => {
610
- if (!isConnected.value) {
611
- const data = await api.getStats();
612
- stats.value = data;
613
- }
614
- }, 5000);
615
- };
616
-
617
- // Lifecycle
618
- onMounted(() => {
619
- connect();
620
- startPolling();
621
- });
622
-
623
- onBeforeUnmount(() => {
624
- if (websocket) {
625
- websocket.close();
626
- }
627
- clearTimeout(reconnectTimeout);
628
- });
629
- ```
630
-
631
- ---
632
-
633
- ## Scenario 7: Inline Editing Table
634
-
635
- **Use Case:** Edit table cells directly without opening form
636
-
637
- **Prompt for AI:**
638
- ```
639
- Create a product price management grid with inline editing.
640
-
641
- Table columns:
642
- - Product name (read-only)
643
- - SKU (read-only)
644
- - Cost price (editable, currency)
645
- - Selling price (editable, currency)
646
- - Margin % (calculated, read-only)
647
- - Stock (editable, number)
648
- - Status (editable, select: active, inactive)
649
- - Actions (save, cancel)
650
-
651
- Features:
652
- - Click cell to edit (input appears)
653
- - Press Enter to save
654
- - Press Escape to cancel
655
- - Click outside cell to save
656
- - Show loading indicator while saving
657
- - Highlight row when editing
658
- - Validate on blur
659
- - Show error message inline
660
- - Undo changes button
661
- - Bulk edit selected rows
662
- ```
663
-
664
- **Expected Implementation:**
665
- ```typescript
666
- const editingCell = ref<{rowId: string; field: string} | null>(null);
667
- const originalValues = new Map();
668
-
669
- const startEdit = (rowId: string, field: string, currentValue: any) => {
670
- editingCell.value = { rowId, field };
671
- originalValues.set(`${rowId}-${field}`, currentValue);
672
- };
673
-
674
- const saveEdit = async (row: any, field: string) => {
675
- try {
676
- await api.updateProduct(row.id, { [field]: row[field] });
677
- notification.success('Updated');
678
- editingCell.value = null;
679
- originalValues.delete(`${row.id}-${field}`);
680
- } catch (error) {
681
- notification.error('Failed to update');
682
- }
683
- };
684
-
685
- const cancelEdit = (row: any, field: string) => {
686
- const originalValue = originalValues.get(`${row.id}-${field}`);
687
- if (originalValue !== undefined) {
688
- row[field] = originalValue;
689
- }
690
- editingCell.value = null;
691
- originalValues.delete(`${row.id}-${field}`);
692
- };
693
-
694
- const isEditing = (rowId: string, field: string) => {
695
- return editingCell.value?.rowId === rowId &&
696
- editingCell.value?.field === field;
697
- };
698
- ```
699
-
700
- ```vue
701
- <template>
702
- <VcTable :items="products">
703
- <template #item_price="{ item }">
704
- <div v-if="isEditing(item.id, 'price')">
705
- <VcInputCurrency
706
- v-model="item.price"
707
- @blur="saveEdit(item, 'price')"
708
- @keyup.enter="saveEdit(item, 'price')"
709
- @keyup.esc="cancelEdit(item, 'price')"
710
- autofocus
711
- />
712
- </div>
713
- <div
714
- v-else
715
- @click="startEdit(item.id, 'price', item.price)"
716
- class="editable-cell"
717
- >
718
- {{ formatCurrency(item.price) }}
719
- </div>
720
- </template>
721
- </VcTable>
722
- </template>
723
- ```
724
-
725
- ---
726
-
727
- ## Scenario 8: Conditional Workflow
728
-
729
- **Use Case:** Approval workflow with different paths
730
-
731
- **Prompt for AI:**
732
- ```
733
- Create a document approval system with conditional workflow.
734
-
735
- Document states: Draft → Submitted → Approved/Rejected → Published
736
-
737
- Rules:
738
- - Author can: Create, Edit (if Draft), Submit, Cancel
739
- - Reviewer can: Approve, Reject, Request Changes
740
- - Admin can: Override, Publish, Archive
741
-
742
- Workflow:
743
- 1. Author creates document (Draft)
744
- 2. Author submits for review (Submitted)
745
- 3. If approved → can publish
746
- 4. If rejected → back to Draft
747
- 5. If changes requested → back to Draft with comments
748
- 6. Admin can override any state
749
-
750
- Show:
751
- - Current state badge
752
- - Available actions (buttons) based on user role and state
753
- - History timeline (state changes, who did what, when)
754
- - Comments thread
755
- - Approval/rejection reason required
756
-
757
- Implement state machine pattern.
758
- Validate state transitions.
759
- Send notifications on state changes.
760
- ```
761
-
762
- **Expected Implementation:**
763
- ```typescript
764
- type DocumentState = 'draft' | 'submitted' | 'approved' | 'rejected' | 'published' | 'archived';
765
- type UserRole = 'author' | 'reviewer' | 'admin';
766
-
767
- interface StateTransition {
768
- from: DocumentState;
769
- to: DocumentState;
770
- roles: UserRole[];
771
- requiresComment?: boolean;
772
- }
773
-
774
- const transitions: StateTransition[] = [
775
- { from: 'draft', to: 'submitted', roles: ['author'] },
776
- { from: 'submitted', to: 'approved', roles: ['reviewer', 'admin'] },
777
- { from: 'submitted', to: 'rejected', roles: ['reviewer', 'admin'], requiresComment: true },
778
- { from: 'approved', to: 'published', roles: ['author', 'admin'] },
779
- { from: 'rejected', to: 'draft', roles: ['author'] },
780
- // Admin overrides
781
- { from: 'draft', to: 'published', roles: ['admin'] },
782
- { from: 'published', to: 'archived', roles: ['admin'] }
783
- ];
784
-
785
- const canTransition = (from: DocumentState, to: DocumentState, role: UserRole): boolean => {
786
- return transitions.some(t =>
787
- t.from === from && t.to === to && t.roles.includes(role)
788
- );
789
- };
790
-
791
- const availableActions = computed(() => {
792
- const currentState = document.value.state;
793
- const userRole = currentUser.value.role;
794
-
795
- return transitions
796
- .filter(t => t.from === currentState && t.roles.includes(userRole))
797
- .map(t => ({
798
- action: t.to,
799
- label: getActionLabel(t.to),
800
- requiresComment: t.requiresComment
801
- }));
802
- });
803
-
804
- const performTransition = async (targetState: DocumentState, comment?: string) => {
805
- if (!canTransition(document.value.state, targetState, currentUser.value.role)) {
806
- notification.error('Action not allowed');
807
- return;
808
- }
809
-
810
- const transition = transitions.find(t =>
811
- t.from === document.value.state && t.to === targetState
812
- );
813
-
814
- if (transition?.requiresComment && !comment) {
815
- notification.error('Comment required for this action');
816
- return;
817
- }
818
-
819
- try {
820
- await api.updateDocumentState(document.value.id, {
821
- newState: targetState,
822
- comment,
823
- userId: currentUser.value.id
824
- });
825
-
826
- document.value.state = targetState;
827
-
828
- // Add to history
829
- document.value.history.push({
830
- timestamp: new Date(),
831
- user: currentUser.value.name,
832
- action: targetState,
833
- comment
834
- });
835
-
836
- notification.success(`Document ${targetState}`);
837
-
838
- } catch (error) {
839
- notification.error('Failed to update state');
840
- }
841
- };
842
- ```
843
-
844
- ---
845
-
846
- ## Related Documentation
847
-
848
- - [CLI Usage](./cli-usage.md) - Generate modules
849
- - [Quick Start Scenarios](./quick-start-scenarios.md) - Common modules
850
- - [Complete Workflow](../guides/complete-workflow.md) - Development process
851
- - [Troubleshooting Guide](../guides/troubleshooting.md) - Solve common issues
852
-