@vc-shell/vc-app-skill 2.0.0-alpha.23 → 2.0.0-alpha.25

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 (45) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/knowledge-stats.cjs +135 -0
  3. package/bin/sync-docs.cjs +62 -0
  4. package/package.json +5 -1
  5. package/runtime/VERSION +1 -1
  6. package/runtime/agents/details-blade-generator.md +75 -14
  7. package/runtime/knowledge/docs/_BUILD_HASH.md +1 -1
  8. package/runtime/knowledge/docs/core/api/platform.docs.md +14 -7
  9. package/runtime/knowledge/docs/core/blade-navigation/blade-nav-composables.docs.md +6 -0
  10. package/runtime/knowledge/docs/core/composables/useApiClient/useApiClient.docs.md +6 -0
  11. package/runtime/knowledge/docs/core/composables/useAssetsManager/useAssetsManager.docs.md +149 -0
  12. package/runtime/knowledge/docs/core/composables/useAsync/useAsync.docs.md +7 -0
  13. package/runtime/knowledge/docs/core/composables/useBlade/useBlade.docs.md +7 -0
  14. package/runtime/knowledge/docs/core/composables/useBladeWidgets.docs.md +164 -9
  15. package/runtime/knowledge/docs/core/composables/useDashboard/useDashboard.docs.md +6 -0
  16. package/runtime/knowledge/docs/core/composables/useMenuService/useMenuService.docs.md +6 -0
  17. package/runtime/knowledge/docs/core/composables/usePermissions/usePermissions.docs.md +6 -0
  18. package/runtime/knowledge/docs/core/composables/useToolbar/useToolbar.docs.md +6 -0
  19. package/runtime/knowledge/docs/core/composables/useWidgets/useWidgets.docs.md +2 -2
  20. package/runtime/knowledge/docs/core/plugins/ai-agent/ai-agent.docs.md +6 -0
  21. package/runtime/knowledge/docs/core/plugins/extension-points/extension-points.docs.md +6 -0
  22. package/runtime/knowledge/docs/core/plugins/global-error-handler/global-error-handler.docs.md +6 -0
  23. package/runtime/knowledge/docs/core/plugins/i18n/i18n.docs.md +74 -0
  24. package/runtime/knowledge/docs/core/plugins/modularity/modularity.docs.md +6 -0
  25. package/runtime/knowledge/docs/core/plugins/permissions/permissions.docs.md +6 -0
  26. package/runtime/knowledge/docs/core/plugins/signalR/signalR.docs.md +6 -0
  27. package/runtime/knowledge/docs/core/plugins/validation/validation.docs.md +6 -0
  28. package/runtime/knowledge/docs/injection-keys.docs.md +1 -1
  29. package/runtime/knowledge/docs/modules/assets-manager/assets-manager.docs.md +29 -33
  30. package/runtime/knowledge/docs/shell/components/logout-button/logout-button.docs.md +3 -3
  31. package/runtime/knowledge/docs/ui/components/atoms/vc-button/vc-button.docs.md +11 -0
  32. package/runtime/knowledge/docs/ui/components/atoms/vc-card/vc-card.docs.md +11 -0
  33. package/runtime/knowledge/docs/ui/components/organisms/vc-blade/vc-blade.docs.md +11 -0
  34. package/runtime/knowledge/docs/ui/components/organisms/vc-table/vc-data-table.docs.md +12 -0
  35. package/runtime/knowledge/index.md +60 -0
  36. package/runtime/knowledge/patterns/assets-management.md +213 -0
  37. package/runtime/knowledge/patterns/child-blade-flow.md +277 -0
  38. package/runtime/knowledge/patterns/details-blade-pattern.md +350 -3
  39. package/runtime/knowledge/patterns/extension-points-usage.md +308 -0
  40. package/runtime/knowledge/patterns/form-validation.md +377 -0
  41. package/runtime/knowledge/patterns/multilanguage-fields.md +239 -0
  42. package/runtime/knowledge/patterns/signalr-notifications.md +237 -0
  43. package/runtime/vc-app.md +44 -5
  44. package/runtime/knowledge/docs/core/composables/useWidget/useWidget.docs.md +0 -159
  45. package/runtime/knowledge/docs/shell/components/app-switcher/app-switcher.docs.md +0 -104
@@ -115,7 +115,7 @@ A details blade is a form-based panel opened from a list blade (or navigation) t
115
115
  :label="$t('MODULE.FIELDS.IS_ACTIVE.LABEL')"
116
116
  />
117
117
 
118
- <!-- Date/DateTime field: VcInput type="datetime-local" -->
118
+ <!-- Date/DateTime field: VcDatePicker (preferred over VcInput type="datetime-local") -->
119
119
  <Field
120
120
  v-slot="{ errorMessage, handleChange, errors }"
121
121
  :label="$t('MODULE.FIELDS.CREATED_DATE.LABEL')"
@@ -123,10 +123,9 @@ A details blade is a form-based panel opened from a list blade (or navigation) t
123
123
  name="createdDate"
124
124
  rules="required"
125
125
  >
126
- <VcInput
126
+ <VcDatePicker
127
127
  v-model="entity.createdDate"
128
128
  class="tw-p-3"
129
- type="datetime-local"
130
129
  :label="$t('MODULE.FIELDS.CREATED_DATE.LABEL')"
131
130
  required
132
131
  :error="!!errors.length"
@@ -134,6 +133,143 @@ A details blade is a form-based panel opened from a list blade (or navigation) t
134
133
  @update:model-value="handleChange"
135
134
  />
136
135
  </Field>
136
+
137
+ <!-- Textarea field: for long text (description, notes, comments) -->
138
+ <Field
139
+ v-slot="{ errorMessage, handleChange, errors }"
140
+ :label="$t('MODULE.FIELDS.DESCRIPTION.LABEL')"
141
+ :model-value="entity.description"
142
+ name="description"
143
+ >
144
+ <VcTextarea
145
+ v-model="entity.description"
146
+ class="tw-p-3"
147
+ :label="$t('MODULE.FIELDS.DESCRIPTION.LABEL')"
148
+ :placeholder="$t('MODULE.FIELDS.DESCRIPTION.PLACEHOLDER')"
149
+ :error="!!errors.length"
150
+ :error-message="errorMessage"
151
+ @update:model-value="handleChange"
152
+ />
153
+ </Field>
154
+
155
+ <!-- Rich text / HTML field: VcEditor (WYSIWYG) -->
156
+ <Field
157
+ v-slot="{ errorMessage, handleChange, errors }"
158
+ :label="$t('MODULE.FIELDS.BODY.LABEL')"
159
+ :model-value="entity.body"
160
+ name="body"
161
+ >
162
+ <VcEditor
163
+ v-model="entity.body"
164
+ class="tw-p-3"
165
+ :label="$t('MODULE.FIELDS.BODY.LABEL')"
166
+ :error="!!errors.length"
167
+ :error-message="errorMessage"
168
+ @update:model-value="handleChange"
169
+ />
170
+ </Field>
171
+
172
+ <!-- Currency field: VcInputCurrency (formatted monetary input) -->
173
+ <Field
174
+ v-slot="{ errorMessage, handleChange, errors }"
175
+ :label="$t('MODULE.FIELDS.PRICE.LABEL')"
176
+ :model-value="entity.price"
177
+ name="price"
178
+ rules="required|min_value:0"
179
+ >
180
+ <VcInputCurrency
181
+ v-model="entity.price"
182
+ class="tw-p-3"
183
+ :label="$t('MODULE.FIELDS.PRICE.LABEL')"
184
+ currency="USD"
185
+ required
186
+ :error="!!errors.length"
187
+ :error-message="errorMessage"
188
+ @update:model-value="handleChange"
189
+ />
190
+ </Field>
191
+
192
+ <!-- Multi-select / Tags field: VcMultivalue -->
193
+ <VcMultivalue
194
+ v-model="entity.tags"
195
+ class="tw-p-3"
196
+ :label="$t('MODULE.FIELDS.TAGS.LABEL')"
197
+ :placeholder="$t('MODULE.FIELDS.TAGS.PLACEHOLDER')"
198
+ />
199
+
200
+ <!-- Checkbox field: alternative to VcSwitch for accept/agree semantics -->
201
+ <VcCheckbox
202
+ v-model="entity.agreeTerms"
203
+ class="tw-p-3"
204
+ >
205
+ {{ $t('MODULE.FIELDS.AGREE_TERMS.LABEL') }}
206
+ </VcCheckbox>
207
+
208
+ <!-- Radio group: for enum fields with 2-5 options -->
209
+ <VcRadioGroup
210
+ v-model="entity.priority"
211
+ class="tw-p-3"
212
+ :label="$t('MODULE.FIELDS.PRIORITY.LABEL')"
213
+ :options="priorityOptions"
214
+ option-value="id"
215
+ option-label="name"
216
+ />
217
+
218
+ <!-- Rating field: VcRating -->
219
+ <VcRating
220
+ v-model="entity.rating"
221
+ class="tw-p-3"
222
+ :label="$t('MODULE.FIELDS.RATING.LABEL')"
223
+ />
224
+
225
+ <!-- Slider field: VcSlider (numeric range) -->
226
+ <VcSlider
227
+ v-model="entity.discount"
228
+ class="tw-p-3"
229
+ :label="$t('MODULE.FIELDS.DISCOUNT.LABEL')"
230
+ :min="0"
231
+ :max="100"
232
+ />
233
+
234
+ <!-- Color field: VcColorInput -->
235
+ <VcColorInput
236
+ v-model="entity.brandColor"
237
+ class="tw-p-3"
238
+ :label="$t('MODULE.FIELDS.BRAND_COLOR.LABEL')"
239
+ />
240
+
241
+ <!-- Image upload field: VcImageUpload -->
242
+ <VcImageUpload
243
+ v-model="entity.avatar"
244
+ class="tw-p-3"
245
+ :label="$t('MODULE.FIELDS.AVATAR.LABEL')"
246
+ />
247
+
248
+ <!-- File upload field: VcFileUpload -->
249
+ <VcFileUpload
250
+ v-model="entity.attachment"
251
+ class="tw-p-3"
252
+ :label="$t('MODULE.FIELDS.ATTACHMENT.LABEL')"
253
+ :accept="'.pdf,.doc,.docx'"
254
+ />
255
+ </VcCol>
256
+ </VcRow>
257
+
258
+ <!-- Form sections with VcCard for visual grouping -->
259
+ <VcRow>
260
+ <VcCol>
261
+ <VcCard :header="$t('MODULE.SECTIONS.ADVANCED.TITLE')">
262
+ <!-- Group related fields inside a card -->
263
+ </VcCard>
264
+ </VcCol>
265
+ </VcRow>
266
+
267
+ <!-- Collapsible sections with VcAccordion -->
268
+ <VcRow>
269
+ <VcCol>
270
+ <VcAccordion :header="$t('MODULE.SECTIONS.SETTINGS.TITLE')">
271
+ <!-- Fields that are less frequently edited -->
272
+ </VcAccordion>
137
273
  </VcCol>
138
274
  </VcRow>
139
275
 
@@ -367,6 +503,163 @@ The `VcDataTable` here is read-only — no toolbar, no selection, no pagination
367
503
 
368
504
  ---
369
505
 
506
+ ## Read-Only Details Pattern (VcField)
507
+
508
+ For details blades that display data without editing (e.g., order details, transaction history), use `VcField` instead of `VcInput`. This is a common pattern for view-only pages.
509
+
510
+ Source: `apps/vendor-portal/src/modules/orders/pages/order-details.vue`
511
+
512
+ ```vue
513
+ <VcCard :header="$t('MODULE.SECTIONS.INFO.TITLE')">
514
+ <div class="tw-p-4 tw-space-y-4">
515
+ <VcField
516
+ :label="$t('MODULE.FIELDS.ORDER_REF.LABEL')"
517
+ :model-value="entity.number || entity.id"
518
+ orientation="horizontal"
519
+ :aspect-ratio="[1, 2]"
520
+ copyable
521
+ type="text"
522
+ />
523
+ <VcField
524
+ :label="$t('MODULE.FIELDS.STATUS.LABEL')"
525
+ :model-value="entity.status"
526
+ orientation="horizontal"
527
+ :aspect-ratio="[1, 2]"
528
+ type="text"
529
+ :tooltip="$t('MODULE.FIELDS.STATUS.TOOLTIP')"
530
+ />
531
+ <VcField
532
+ :label="$t('MODULE.FIELDS.TOTAL.LABEL')"
533
+ :model-value="withCurrency(entity.total)"
534
+ orientation="horizontal"
535
+ :aspect-ratio="[1, 2]"
536
+ type="text"
537
+ class="tw-font-semibold"
538
+ />
539
+ </div>
540
+ </VcCard>
541
+ ```
542
+
543
+ Key `VcField` props:
544
+ - `orientation="horizontal"` — label left, value right (default is vertical)
545
+ - `:aspect-ratio="[1, 2]"` — label takes 1/3, value takes 2/3
546
+ - `copyable` — adds a copy-to-clipboard button
547
+ - `:tooltip` — adds an info icon with hover text
548
+
549
+ Use `VcField` when:
550
+ - The blade is read-only (order details, audit log, transaction view)
551
+ - Showing summary/computed data that users don't edit
552
+ - Displaying key-value pairs in a structured layout
553
+
554
+ ---
555
+
556
+ ## Contextual Banners Pattern (VcBanner)
557
+
558
+ Use `VcBanner` at the top of a form to show contextual alerts, warnings, or informational messages based on entity state.
559
+
560
+ Source: `apps/vendor-portal/src/modules/offers/pages/offers-details.vue`, `seller-details-edit.vue`
561
+
562
+ ```vue
563
+ <!-- Error banner (shown conditionally) -->
564
+ <VcBanner
565
+ v-if="errorMessage"
566
+ variant="danger"
567
+ icon="lucide-alert-circle"
568
+ icon-size="l"
569
+ icon-variant="danger"
570
+ >
571
+ <template #title>
572
+ {{ $t("MODULE.ALERTS.ERROR") }}
573
+ </template>
574
+ <template #default>
575
+ {{ errorMessage }}
576
+ </template>
577
+ </VcBanner>
578
+
579
+ <!-- Info banner with action button -->
580
+ <VcBanner
581
+ v-if="!entity.id"
582
+ variant="info"
583
+ icon="lucide-lightbulb"
584
+ icon-size="l"
585
+ >
586
+ {{ $t("MODULE.ALERTS.CREATE_NEW_HINT") }}
587
+ </VcBanner>
588
+
589
+ <!-- Warning banner with inline action -->
590
+ <VcBanner
591
+ v-if="entity.id && !entity.priceLists?.length"
592
+ variant="info"
593
+ icon="lucide-lightbulb"
594
+ icon-size="l"
595
+ >
596
+ <div class="tw-flex tw-flex-row tw-justify-between tw-items-center">
597
+ <span>{{ $t("MODULE.ALERTS.MISSING_PRICES") }}</span>
598
+ <VcButton
599
+ icon="lucide-plus"
600
+ variant="link"
601
+ size="sm"
602
+ class="tw-shrink-0"
603
+ @click="openBlade({ name: 'PricesList' })"
604
+ >{{ $t("MODULE.ACTIONS.ADD_PRICE") }}</VcButton>
605
+ </div>
606
+ </VcBanner>
607
+ ```
608
+
609
+ Banner variants: `"info"`, `"warning"`, `"danger"`, `"success"`.
610
+
611
+ Use banners when:
612
+ - Entity is in a special state (new, error, incomplete)
613
+ - User needs to take action (missing required data)
614
+ - Showing validation errors at form level
615
+
616
+ ---
617
+
618
+ ## Blade Widgets Pattern (useBladeWidgets)
619
+
620
+ Blade widgets appear as icon buttons in the blade sidebar, showing related entities with badge counts. Clicking a widget opens a child blade.
621
+
622
+ Source: `apps/vendor-portal/src/modules/products/widgets/useProductWidgets.ts`
623
+
624
+ ```ts
625
+ import { useBladeWidgets, type UseBladeWidgetsReturn } from "@vc-shell/framework";
626
+
627
+ export function useProductWidgets(options: { item: Ref<Product | undefined> }): UseBladeWidgetsReturn {
628
+ const { item } = options;
629
+ const { openBlade } = useBlade();
630
+
631
+ const { count: offersCount, loading: offersLoading, refresh: refreshOffers } = useOfferCount(item);
632
+
633
+ const widgets = useBladeWidgets([
634
+ {
635
+ id: "OffersWidget",
636
+ icon: "lucide-tag",
637
+ title: "PRODUCTS.PAGES.DETAILS.WIDGETS.OFFERS",
638
+ badge: offersCount,
639
+ loading: offersLoading,
640
+ isVisible: computed(() => !!item.value?.id),
641
+ onClick: () => openBlade({ name: "Offers", options: { product: item.value } }),
642
+ onRefresh: refreshOffers,
643
+ },
644
+ // ... more widgets
645
+ ]);
646
+
647
+ return widgets;
648
+ }
649
+ ```
650
+
651
+ Use in the blade:
652
+ ```ts
653
+ const { widgets } = useProductWidgets({ item: entity });
654
+ ```
655
+
656
+ Use widgets when:
657
+ - Entity has related sub-entities (product → offers, videos, assets)
658
+ - You want sidebar navigation with counts
659
+ - Each widget opens a different child blade
660
+
661
+ ---
662
+
370
663
  ## Key Rules
371
664
 
372
665
  1. **`defineBlade({ name: "XxxDetails" })`** — no `url`, no `isWorkspace` for blades opened from list. Add `url` + `isWorkspace: true` only for standalone workspace blades (settings pages, profile pages).
@@ -376,3 +669,57 @@ The `VcDataTable` here is read-only — no toolbar, no selection, no pagination
376
669
  5. **After save/delete**: always `callParent("reload")` first, then `closeSelf()`.
377
670
  6. **`isVisible`** on toolbar items: use `!!param.value` for edit-only actions (save, reset, delete), `!param.value` for create-only actions.
378
671
  7. **`disabled: computed(...)`** on toolbar items must be a computed, not a plain boolean, for reactivity.
672
+
673
+ ---
674
+
675
+ ## Field Type → Component Mapping
676
+
677
+ Use this table to choose the correct component for each field type during generation. The `formField.type` value comes from the design prompt analysis (Phase 2 of `/vc-app design`) or from API type inference.
678
+
679
+ | Field Type | Component | When to Use | Notes |
680
+ |---|---|---|---|
681
+ | `string` | `VcInput` | Default for short text (name, title, email, phone, URL) | Use `type="number"` for numeric strings, `rules="email"` for emails |
682
+ | `text` | `VcTextarea` | Long text: description, notes, comments, bio | Multi-line input, no formatting |
683
+ | `rich-text` | `VcEditor` | HTML content: article body, email template, product description | WYSIWYG HTML editor |
684
+ | `number` | `VcInput` | Plain numbers: quantity, count, age, priority | Set `type="number"`, use `rules="bigint\|min_value:0"` |
685
+ | `currency` | `VcInputCurrency` | Money: price, cost, amount, salary, budget | Formatted with currency symbol and decimals |
686
+ | `boolean` | `VcSwitch` | Toggle: isActive, isEnabled, isPublished | No `Field` wrapper needed |
687
+ | `boolean` | `VcCheckbox` | Consent/agree: agreeTerms, acceptPolicy | Use when "agree/accept" semantics, not toggle |
688
+ | `date-time` | `VcDatePicker` | Date/datetime: createdDate, deadline, startDate, birthday | Calendar picker, preferred over `VcInput type="datetime-local"` |
689
+ | `enum` | `VcSelect` | Dropdown: status, type, category (6+ options or dynamic) | Use `option-value` + `option-label` |
690
+ | `enum` | `VcRadioGroup` | Radio: priority, type (2-5 static options) | More visual for small option sets |
691
+ | `multi-select` | `VcMultivalue` | Tags/multi-pick: tags, categories, permissions, roles | Tag-style chips with add/remove |
692
+ | `multi-select` | `VcCheckboxGroup` | Multi-check: features, capabilities (few static options) | Vertical checkbox list |
693
+ | `rating` | `VcRating` | Star rating: rating, score, quality | 1-5 star picker |
694
+ | `range` | `VcSlider` | Range: discount, opacity, volume, percentage | Numeric slider with min/max |
695
+ | `color` | `VcColorInput` | Color: brandColor, backgroundColor, themeColor | Color picker with hex input |
696
+ | `image` | `VcImageUpload` | Single image: avatar, logo, thumbnail, banner | Upload with preview |
697
+ | `gallery` | `VcGallery` | Multiple images: productPhotos, screenshots | Multi-image management grid |
698
+ | `file` | `VcFileUpload` | Attachment: document, contract, certificate | File upload with accept filter |
699
+
700
+ ### Selection heuristics (when type is ambiguous)
701
+
702
+ When the field type from prompt analysis is plain `"string"`, use field name patterns to upgrade:
703
+
704
+ | Field name pattern | Upgrade to |
705
+ |---|---|
706
+ | `description`, `notes`, `comment`, `bio`, `summary`, `about` | `text` → `VcTextarea` |
707
+ | `body`, `content`, `html`, `template`, `article` | `rich-text` → `VcEditor` |
708
+ | `price`, `cost`, `amount`, `total`, `salary`, `budget`, `fee` | `currency` → `VcInputCurrency` |
709
+ | `avatar`, `logo`, `photo`, `thumbnail`, `banner`, `icon` | `image` → `VcImageUpload` |
710
+ | `photos`, `images`, `screenshots`, `gallery` | `gallery` → `VcGallery` |
711
+ | `file`, `attachment`, `document`, `contract`, `certificate` | `file` → `VcFileUpload` |
712
+ | `tags`, `labels`, `categories`, `roles`, `permissions` | `multi-select` → `VcMultivalue` |
713
+ | `rating`, `score`, `stars` | `rating` → `VcRating` |
714
+ | `color`, `colour`, `brandColor`, `backgroundColor` | `color` → `VcColorInput` |
715
+ | `discount`, `opacity`, `volume`, `percentage`, `progress` | `range` → `VcSlider` |
716
+ | `email` | `string` → `VcInput` with `rules="required\|email"` |
717
+ | `phone`, `tel` | `string` → `VcInput` with `type="tel"` |
718
+ | `url`, `website`, `link` | `string` → `VcInput` with `type="url"` |
719
+
720
+ ### Layout heuristics
721
+
722
+ - **3+ fields in a form** → use `VcRow` / `VcCol` grid to arrange fields in 2 columns
723
+ - **5+ fields with logical groups** → wrap groups in `VcCard` with a section header
724
+ - **8+ fields** → consider `VcAccordion` for secondary/advanced fields
725
+ - **Mixed required + optional fields** → put required fields at the top, optional in a collapsible section
@@ -0,0 +1,308 @@
1
+ # Extension Points Usage Pattern
2
+
3
+ Cross-module UI composition without compile-time dependencies. One module declares a named slot (host), other modules inject components into it (plugin). Fully reactive and order-independent.
4
+
5
+ ---
6
+
7
+ ## When to Use
8
+
9
+ - Module A needs to inject UI into Module B without importing it (e.g., a "Commissions" section inside a "Seller Details" blade owned by another module).
10
+ - Modules load in any order (including remote Module Federation modules).
11
+
12
+ **Do not use** for simple parent-child communication within one module -- use props/slots/provide-inject instead.
13
+
14
+ ---
15
+
16
+ ## Core Concepts
17
+
18
+ ### Two Roles
19
+
20
+ | Role | API | Purpose |
21
+ |------|-----|---------|
22
+ | **Host** | `<ExtensionPoint>` or `defineExtensionPoint()` | Declares a named region that accepts plugins |
23
+ | **Plugin** | `useExtensionPoint()` | Registers components into a named region |
24
+
25
+ Neither side imports the other. They communicate through a shared **name string**.
26
+
27
+ ### Order Independence
28
+
29
+ Plugins can register components **before** the host declares the point. The reactive store preserves pre-registered components and upgrades the entry when the host declares.
30
+
31
+ ### Priority Sorting
32
+
33
+ Components are sorted by `priority` (ascending, default `0`). Same priority = registration order.
34
+
35
+ ---
36
+
37
+ ## Inbound Extensions (Host Accepts Plugins)
38
+
39
+ A module declares a named extension point in its blade template. Other modules inject components into it.
40
+
41
+ ### Declarative: `<ExtensionPoint>` Component
42
+
43
+ The simplest approach -- declares the point and renders registered components in one step:
44
+
45
+ ```vue
46
+ <!-- seller-details/pages/seller-details-edit.vue -->
47
+ <template>
48
+ <VcBlade title="Seller Details">
49
+ <form><!-- main form fields --></form>
50
+
51
+ <ExtensionPoint
52
+ v-if="sellerDetails?.id"
53
+ name="seller:commissions"
54
+ wrapper-class="tw-p-2"
55
+ />
56
+ </VcBlade>
57
+ </template>
58
+
59
+ <script setup lang="ts">
60
+ import { ExtensionPoint } from "@vc-shell/framework";
61
+ </script>
62
+ ```
63
+
64
+ **Props:**
65
+
66
+ | Prop | Type | Default | Description |
67
+ |------|------|---------|-------------|
68
+ | `name` | `string` | required | Extension point name |
69
+ | `separator` | `boolean` | `false` | Adds `<hr>` before components |
70
+ | `separatorClass` | `string` | built-in | Custom CSS class for `<hr>` |
71
+ | `wrapperClass` | `string` | none | CSS class for wrapper div |
72
+ | `gap` | `string` | none | CSS gap (e.g. `"1rem"`) |
73
+ | `filter` | `Record<string, unknown>` | none | Filter by meta field equality |
74
+
75
+ ### Composable: `defineExtensionPoint()`
76
+
77
+ For programmatic access to registered components (e.g., building tabs dynamically):
78
+
79
+ ```typescript
80
+ import { defineExtensionPoint } from "@vc-shell/framework";
81
+
82
+ interface TabMeta {
83
+ zone: "tabs" | "toolbar";
84
+ label: string;
85
+ }
86
+
87
+ const { components, hasComponents } = defineExtensionPoint<TabMeta>("product:details");
88
+
89
+ // components.value is ComputedRef<ExtensionComponent[]>, sorted by priority
90
+ // hasComponents.value is ComputedRef<boolean>
91
+ ```
92
+
93
+ ### Scoped Slot for Custom Rendering
94
+
95
+ When the host needs full control over layout:
96
+
97
+ ```vue
98
+ <ExtensionPoint name="order:actions" v-slot="{ components, hasComponents }">
99
+ <div v-if="hasComponents" class="tw-flex tw-gap-2">
100
+ <component
101
+ v-for="ext in components"
102
+ :key="ext.id"
103
+ :is="ext.component"
104
+ v-bind="ext.props || {}"
105
+ />
106
+ </div>
107
+ </ExtensionPoint>
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Outbound Extensions (Plugin Provides Components)
113
+
114
+ A module registers components into an extension point declared by another module (or the framework itself).
115
+
116
+ ### Basic Registration
117
+
118
+ ```typescript
119
+ // commissions-module/index.ts
120
+ import { defineAppModule, useExtensionPoint } from "@vc-shell/framework";
121
+ import CommissionFields from "./components/CommissionFields.vue";
122
+
123
+ const { add } = useExtensionPoint("seller:commissions");
124
+ add({
125
+ id: "marketplace:commission-fields",
126
+ component: CommissionFields,
127
+ props: { editable: true },
128
+ priority: 10,
129
+ });
130
+
131
+ export default defineAppModule({ /* locales, blades, etc. */ });
132
+ ```
133
+
134
+ ### Using Framework Constants
135
+
136
+ The framework exposes typed constants for its own extension points:
137
+
138
+ ```typescript
139
+ import { useExtensionPoint, ExtensionPoints } from "@vc-shell/framework";
140
+ import RegistrationButton from "./components/RegistrationButton.vue";
141
+
142
+ const { add } = useExtensionPoint(ExtensionPoints.AUTH_AFTER_FORM);
143
+ add({
144
+ id: "registration:signup-button",
145
+ component: RegistrationButton,
146
+ meta: { type: "action" },
147
+ });
148
+ ```
149
+
150
+ Currently defined framework constants:
151
+
152
+ | Constant | Value | Location |
153
+ |----------|-------|----------|
154
+ | `ExtensionPoints.AUTH_AFTER_FORM` | `"auth:after-form"` | Login page, below sign-in form |
155
+
156
+ ### Removing a Registration
157
+
158
+ ```typescript
159
+ const { add, remove } = useExtensionPoint("seller:commissions");
160
+ add({ id: "my-fields", component: MyFields });
161
+
162
+ // Later (e.g., during cleanup):
163
+ remove("my-fields");
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Passing Props and Context to Injected Components
169
+
170
+ Props are passed via the `props` field in `add()` and bound with `v-bind`:
171
+
172
+ ```typescript
173
+ add({
174
+ id: "commission-fields",
175
+ component: CommissionFields,
176
+ props: { editable: true, sellerId: "abc-123" },
177
+ priority: 10,
178
+ });
179
+ ```
180
+
181
+ The injected component receives them as standard Vue props:
182
+
183
+ ```vue
184
+ <script setup lang="ts">
185
+ const props = defineProps<{
186
+ editable?: boolean;
187
+ sellerId?: string;
188
+ }>();
189
+ </script>
190
+ ```
191
+
192
+ For dynamic context that changes after registration, use reactive values or provide/inject within the host blade.
193
+
194
+ ---
195
+
196
+ ## Metadata Filtering
197
+
198
+ Categorize extensions with `meta` and render different subsets in different locations:
199
+
200
+ ```typescript
201
+ const { add } = useExtensionPoint("product:details");
202
+ add({ id: "specs-tab", component: SpecsTab, meta: { zone: "tabs" }, priority: 10 });
203
+ add({ id: "review-tab", component: ReviewTab, meta: { zone: "tabs" }, priority: 20 });
204
+ add({ id: "quick-action", component: QuickAction, meta: { zone: "toolbar" } });
205
+ ```
206
+
207
+ ```vue
208
+ <template>
209
+ <VcBlade title="Product Details">
210
+ <div class="toolbar">
211
+ <ExtensionPoint name="product:details" :filter="{ zone: 'toolbar' }" />
212
+ </div>
213
+ <div class="tabs">
214
+ <ExtensionPoint name="product:details" :filter="{ zone: 'tabs' }" />
215
+ </div>
216
+ </VcBlade>
217
+ </template>
218
+ ```
219
+
220
+ Filtering uses strict equality (`===`) on every key-value pair; all pairs must match.
221
+
222
+ ---
223
+
224
+ ## Recipe: Adding a Custom Tab to Another Module's Blade
225
+
226
+ **Step 1 -- Host module declares the point:**
227
+
228
+ ```vue
229
+ <!-- orders/pages/order-details.vue -->
230
+ <template>
231
+ <VcBlade title="Order Details">
232
+ <VcForm><!-- core order fields --></VcForm>
233
+
234
+ <ExtensionPoint
235
+ name="order:details-tabs"
236
+ gap="1rem"
237
+ wrapper-class="tw-mt-4"
238
+ />
239
+ </VcBlade>
240
+ </template>
241
+
242
+ <script setup lang="ts">
243
+ import { ExtensionPoint } from "@vc-shell/framework";
244
+ </script>
245
+ ```
246
+
247
+ **Step 2 -- Plugin module registers a tab component:**
248
+
249
+ ```typescript
250
+ // shipping-module/index.ts
251
+ import { defineAppModule, useExtensionPoint } from "@vc-shell/framework";
252
+ import ShippingTab from "./components/ShippingTab.vue";
253
+
254
+ const { add } = useExtensionPoint("order:details-tabs");
255
+ add({
256
+ id: "shipping:tracking-tab",
257
+ component: ShippingTab,
258
+ props: { showMap: true },
259
+ priority: 20,
260
+ });
261
+
262
+ export default defineAppModule({ /* ... */ });
263
+ ```
264
+
265
+ **Step 3 -- The injected component:**
266
+
267
+ ```vue
268
+ <!-- shipping-module/components/ShippingTab.vue -->
269
+ <template>
270
+ <VcCard header="Shipping & Tracking">
271
+ <div class="tw-p-4">
272
+ <p>Carrier: {{ carrier }}</p>
273
+ <div v-if="showMap"><!-- map widget --></div>
274
+ </div>
275
+ </VcCard>
276
+ </template>
277
+
278
+ <script setup lang="ts">
279
+ import { ref } from "vue";
280
+ defineProps<{ showMap?: boolean }>();
281
+ const carrier = ref("FedEx");
282
+ </script>
283
+ ```
284
+
285
+ ---
286
+
287
+ ## Common Mistakes
288
+
289
+ | Mistake | Symptom | Fix |
290
+ |---------|---------|-----|
291
+ | Typo in extension point name | Component registered but never rendered | Check dev console for `"Extension point X is not declared"` warning. Use shared constants. |
292
+ | Duplicate `id` across modules | Later registration silently replaces the earlier one | Prefix IDs with module name: `"shipping:tracking-tab"` |
293
+ | Forgetting `import { ExtensionPoint }` in host template | Vue renders nothing (unknown component) | Add the import in `<script setup>` |
294
+ | Calling `defineExtensionPoint` outside setup context | Computed refs not reactive | Call inside `<script setup>` or a composable used during setup |
295
+
296
+ ---
297
+
298
+ ## Related
299
+
300
+ | Resource | Path |
301
+ |----------|------|
302
+ | Extension Points Plugin (full docs) | `framework/core/plugins/extension-points/extension-points.docs.md` |
303
+ | Store implementation | `framework/core/plugins/extension-points/store.ts` |
304
+ | ExtensionPoint component | `framework/core/plugins/extension-points/ExtensionPoint.vue` |
305
+ | Types | `framework/core/plugins/extension-points/types.ts` |
306
+ | Public API exports | `framework/core/plugins/extension-points/public.ts` |
307
+ | Real-world host usage | `apps/vendor-portal/src/modules/seller-details/pages/seller-details-edit.vue` |
308
+ | Framework host usage | `framework/shell/auth/LoginPage/components/login/Login.vue` |