@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
@@ -1,15 +1,19 @@
1
- # useBladeWidgets
1
+ # useBladeWidgets / useWidgetTrigger
2
2
 
3
- Declarative headless widget registration for blades. Widgets are automatically registered with the WidgetService on mount and cleaned up on unmount, following the Vue component lifecycle. This composable is the recommended way to add sidebar counter widgets, action buttons, and status indicators to a blade without creating separate Vue component files for each widget.
3
+ Two composables for the widget system one for the **blade side**, one for the **widget side**.
4
4
 
5
- Headless widgets are defined as plain configuration objects with reactive refs for dynamic values like badge counts and loading states. The WidgetService renders them in the blade sidebar.
5
+ | Composable | Called from | Purpose |
6
+ |---|---|---|
7
+ | `useBladeWidgets` | Blade component | Register headless widgets + get `refresh()` / `refreshAll()` |
8
+ | `useWidgetTrigger` | External widget component | Register trigger callbacks (`onRefresh`, `onClick`) via provide/inject |
9
+
10
+ Headless widgets are defined as plain configuration objects with reactive refs for dynamic values like badge counts and loading states. External component-based widgets use `useWidgetTrigger` to register their refresh callbacks so the hosting blade can trigger them.
6
11
 
7
12
  ## When to Use
8
13
 
9
- - Register sidebar widgets (counters, action buttons) for a blade without creating Vue components
10
- - Refresh widget data programmatically after blade operations (save, delete, status change)
11
- - Show/hide widgets conditionally based on blade state (e.g., only show after entity is saved)
12
- - When NOT to use: for widgets that need their own template or complex UI (use a full widget component instead)
14
+ - **`useBladeWidgets`**: Register sidebar widgets (counters, action buttons) for a blade without creating Vue components. Refresh widget data after blade operations (save, delete).
15
+ - **`useWidgetTrigger`**: Inside an external widget component (registered via `registerExternalWidget`) to register `onRefresh` / `onClick` callbacks. The blade can then call `refresh(widgetId)` or `refreshAll()` to trigger them.
16
+ - When NOT to use `useBladeWidgets`: for widgets that need their own template or complex UI (use `registerExternalWidget` + `useWidgetTrigger` instead).
13
17
 
14
18
  ## Basic Usage
15
19
 
@@ -70,6 +74,121 @@ refreshAll();
70
74
  | `refresh` | `(widgetId: string) => void` | Trigger `onRefresh` on a specific widget |
71
75
  | `refreshAll` | `() => void` | Trigger `onRefresh` on all widgets that have one |
72
76
 
77
+ ## useWidgetTrigger
78
+
79
+ Widget-side composable for external component-based widgets. Registers a trigger contract (`onRefresh`, `onClick`, `badge`) via provide/inject — no props, IDs, or service knowledge required.
80
+
81
+ ### Basic Usage
82
+
83
+ ```typescript
84
+ import { useWidgetTrigger } from '@vc-shell/framework';
85
+
86
+ // Inside an external widget component:
87
+ useWidgetTrigger({ onRefresh: loadData });
88
+ ```
89
+
90
+ ### IWidgetTrigger
91
+
92
+ | Field | Type | Required | Description |
93
+ |---|---|---|---|
94
+ | `icon` | `string` | No | Lucide icon name for dropdown rendering |
95
+ | `title` | `string` | No | Display title (fallback: widget's title) |
96
+ | `badge` | `Ref<number \| string>` | No | Reactive badge value |
97
+ | `onClick` | `() => void` | No | Handler called when widget is clicked in dropdown |
98
+ | `onRefresh` | `() => void \| Promise<void>` | No | Handler called to refresh widget data |
99
+ | `disabled` | `Ref<boolean> \| boolean` | No | Disabled state |
100
+
101
+ ### How It Works
102
+
103
+ 1. `WidgetContainer` wraps each component-based widget in a `WidgetScope` provider
104
+ 2. `WidgetScope` provides a `setTrigger` function scoped to the specific widget ID and blade ID
105
+ 3. `useWidgetTrigger` injects this scope and calls `setTrigger` — no props or IDs needed
106
+ 4. When the blade calls `refresh(widgetId)` or `refreshAll()`, the registered `onRefresh` is invoked
107
+
108
+ ## Recipe: External Widget with Refresh
109
+
110
+ A complete example of an external widget that shows an unread message count and supports refresh from the blade:
111
+
112
+ **1. Register the external widget (module index.ts):**
113
+
114
+ ```typescript
115
+ import { createAppModule, registerExternalWidget, IBladeInstance } from "@vc-shell/framework";
116
+ import { markRaw } from "vue";
117
+ import { MessageWidget } from "./components/widgets";
118
+
119
+ registerExternalWidget({
120
+ id: "MessageWidget",
121
+ component: markRaw(MessageWidget),
122
+ targetBlades: ["ProductDetails", "OrderDetails"],
123
+ isVisible: (blade?: IBladeInstance) => !!blade?.param,
124
+ });
125
+ ```
126
+
127
+ **2. Widget component (message-widget.vue):**
128
+
129
+ ```vue
130
+ <template>
131
+ <VcWidget
132
+ v-loading:500="loading"
133
+ :title="$t('MESSENGER.WIDGET.TITLE')"
134
+ icon="lucide-message-circle"
135
+ :value="messageCount"
136
+ @click="openMessageBlade"
137
+ />
138
+ </template>
139
+
140
+ <script setup lang="ts">
141
+ import { ref, computed, onMounted } from "vue";
142
+ import {
143
+ loading as vLoading,
144
+ useBlade,
145
+ injectBladeContext,
146
+ useWidgetTrigger,
147
+ VcWidget,
148
+ } from "@vc-shell/framework";
149
+
150
+ const ctx = injectBladeContext();
151
+ const entityId = computed(() => (ctx.value.item as { id?: string })?.id ?? "");
152
+
153
+ const messageCount = ref(0);
154
+ const loading = ref(false);
155
+
156
+ const loadData = async () => {
157
+ loading.value = true;
158
+ try {
159
+ messageCount.value = await api.getUnreadCount(entityId.value);
160
+ } finally {
161
+ loading.value = false;
162
+ }
163
+ };
164
+
165
+ // Register refresh callback — blade can call refreshAll() after save
166
+ useWidgetTrigger({ onRefresh: loadData });
167
+
168
+ onMounted(() => {
169
+ if (entityId.value) loadData();
170
+ });
171
+ </script>
172
+ ```
173
+
174
+ **3. Blade refreshes widgets after save:**
175
+
176
+ ```vue
177
+ <script setup lang="ts">
178
+ import { useBladeWidgets } from "@vc-shell/framework";
179
+
180
+ // Empty array — blade doesn't register headless widgets,
181
+ // but gets refresh/refreshAll for external widgets
182
+ const { refresh, refreshAll } = useBladeWidgets([]);
183
+
184
+ async function save() {
185
+ await api.saveProduct(product.value);
186
+ refreshAll(); // refresh all widgets (including MessageWidget)
187
+ // or: refresh("MessageWidget"); // refresh a specific widget by ID
188
+ }
189
+ </script>
190
+ ```
191
+
73
192
  ## Recipe: Product Detail Blade with Multiple Widgets
74
193
 
75
194
  ```vue
@@ -126,10 +245,15 @@ async function save() {
126
245
 
127
246
  ## Prerequisites
128
247
 
248
+ **`useBladeWidgets`**:
129
249
  - Must be called inside a blade component rendered by `VcBladeSlot` (requires `BladeDescriptorKey` injection).
130
250
  - `WidgetService` must be provided in the component tree (automatically available in vc-shell apps).
131
251
  - Calling outside a blade context throws an error with a descriptive message.
132
252
 
253
+ **`useWidgetTrigger`**:
254
+ - Must be called inside a widget component rendered by `WidgetContainer` (requires `WidgetScopeKey` injection).
255
+ - If called outside a widget scope, logs a warning and does nothing (does not throw).
256
+
133
257
  ## Details
134
258
 
135
259
  - **Lifecycle management**: Widgets are registered in `onMounted` and unregistered in `onUnmounted`. This ensures the WidgetService always reflects the currently visible blades.
@@ -143,8 +267,39 @@ async function save() {
143
267
  - Keep widget IDs unique within a blade. Duplicate IDs will overwrite previous registrations.
144
268
  - Combine with `defineBladeContext` to expose blade entity data that widget components (non-headless) can consume via `injectBladeContext`.
145
269
 
270
+ ## Common Mistakes
271
+
272
+ ### Calling useWidgetTrigger outside WidgetContainer scope
273
+
274
+ ```typescript
275
+ // Wrong — called in a standalone component, not rendered inside a blade widget slot
276
+ export default defineComponent({
277
+ setup() {
278
+ useWidgetTrigger({ onRefresh: loadData }); // ⚠️ Logs warning, trigger not registered
279
+ },
280
+ });
281
+ ```
282
+
283
+ ```typescript
284
+ // Correct — called inside a widget component registered via registerExternalWidget
285
+ // and rendered by WidgetContainer within a blade
286
+ useWidgetTrigger({ onRefresh: loadData }); // ✓ WidgetScope provides context
287
+ ```
288
+
289
+ ### Forgetting to pass empty array to useBladeWidgets for refresh-only usage
290
+
291
+ ```typescript
292
+ // Wrong — useBladeWidgets requires an array argument
293
+ const { refreshAll } = useBladeWidgets(); // TS error
294
+
295
+ // Correct — pass empty array when you only need refresh/refreshAll
296
+ const { refreshAll } = useBladeWidgets([]);
297
+ ```
298
+
146
299
  ## Related
147
300
 
148
- - `defineBladeContext` -- expose blade data to widgets
149
- - `WidgetService` in `@core/services/widget-service`
301
+ - `defineBladeContext` / `injectBladeContext` -- expose/consume blade data in external widgets
302
+ - `registerExternalWidget` -- register a component-based widget globally for target blades
303
+ - `WidgetService` in `@core/services/widget-service` -- underlying service
304
+ - `WidgetScope` in `vc-blade/_internal/widgets/WidgetScope.vue` -- provides `WidgetScopeKey` to widget components
150
305
  - `VcBladeSlot` -- the blade wrapper that provides `BladeDescriptorKey`
@@ -2,6 +2,12 @@
2
2
 
3
3
  Accesses the dashboard service for registering and managing dashboard widgets. Widgets are permission-aware, support layout positioning, and can be pre-registered during module setup before the service is initialized.
4
4
 
5
+ ## When to Use
6
+
7
+ - Register dashboard widgets during module setup (use standalone `registerDashboardWidget`)
8
+ - Read or update widget layout at runtime inside a dashboard view component (use `useDashboard()`)
9
+ - When NOT to use: for sidebar navigation items -- use `useMenuService` / `addMenuItem`; for settings-page entries -- use `useSettingsMenu`
10
+
5
11
  ## Quick Start
6
12
 
7
13
  ```typescript
@@ -2,6 +2,12 @@
2
2
 
3
3
  Accesses the navigation menu service for adding, removing, and badging menu items in the application sidebar. The service supports a pre-registration pattern that allows modules to declare menu items before the service is initialized, and a grouping system that organizes items into collapsible sections.
4
4
 
5
+ ## When to Use
6
+
7
+ - Register sidebar navigation items during module setup (use standalone `addMenuItem`)
8
+ - Add, remove, or badge menu items at runtime inside components (use `useMenuService()`)
9
+ - When NOT to use: for dashboard widgets -- use `useDashboard` / `registerDashboardWidget`; for settings-page entries -- use `useSettingsMenu`
10
+
5
11
  ## Quick Start
6
12
 
7
13
  ```typescript
@@ -2,6 +2,12 @@
2
2
 
3
3
  Checks whether the current user has specific platform permissions. This composable reads the authenticated user's permission list and provides a `hasAccess` function for conditional UI rendering and access control. It is client-side only -- it does not enforce server-side authorization.
4
4
 
5
+ ## When to Use
6
+
7
+ - Conditionally show or hide UI elements (buttons, toolbar items, sections) based on the current user's permissions
8
+ - Guard blade access or toolbar registration with client-side permission checks
9
+ - When NOT to use: as the sole authorization mechanism -- always pair with server-side enforcement; for checking authentication status -- use `useUser` instead
10
+
5
11
  ## Quick Start
6
12
 
7
13
  ```typescript
@@ -2,6 +2,12 @@
2
2
 
3
3
  Manages toolbar buttons for blades. Each blade in the application has its own toolbar area at the top of the blade header. `useToolbar` provides a scoped API to register, update, and remove buttons within that toolbar. It automatically resolves the current blade context and cleans up registered items when the component unmounts.
4
4
 
5
+ ## When to Use
6
+
7
+ - Add action buttons (Save, Delete, Refresh, Export) to a blade's toolbar header
8
+ - Dynamically update button state (disabled, visible, icon) in response to loading or form changes
9
+ - When NOT to use: for global app-bar actions -- use `useAppBarWidget`; for navigation links -- use `useMenuService`
10
+
5
11
  ## Quick Start
6
12
 
7
13
  ```typescript
@@ -9,7 +9,7 @@ Also exports `provideWidgetService()` for framework-level initialization and `re
9
9
  - When you need low-level access to the widget service (register, unregister, query widgets)
10
10
  - When building framework infrastructure that manages widget lifecycles
11
11
  - When pre-registering widgets from a module's `install()` function before the component tree exists
12
- - When NOT to use: for typical blade widget work, prefer `useBladeWidgets()` which handles lifecycle automatically. For widget-side logic (badge, refresh), use `useWidget()`.
12
+ - When NOT to use: for typical blade widget work, prefer `useBladeWidgets()` which handles lifecycle automatically. For widget-side logic (badge, refresh), use headless widgets.
13
13
 
14
14
  ## Quick Start
15
15
 
@@ -46,7 +46,7 @@ None.
46
46
  | `setActiveWidget` | `(args) => void` | Sets a widget as active with its exposed instance. |
47
47
  | `updateActiveWidget` | `() => void` | Triggers the active widget's update function. Deprecated -- use headless widgets instead. |
48
48
  | `isWidgetRegistered` | `(id: string) => boolean` | Checks if a widget ID exists in any blade's registry. |
49
- | `updateWidget` | `(args) => void` | Updates properties of a registered widget (trigger, badge, etc.). Used by `useWidget().setTrigger()`. |
49
+ | `updateWidget` | `(args) => void` | Updates properties of a registered widget (trigger, badge, etc.). |
50
50
  | `resolveWidgetProps` | `(widget, bladeData) => Record<string, unknown>` | Resolves widget props from blade data. Deprecated. |
51
51
  | `getExternalWidgetsForBlade` | `(bladeId: string) => IExternalWidgetRegistration[]` | Gets external widgets that target a specific blade (registered by other modules). |
52
52
  | `getAllExternalWidgets` | `() => IExternalWidgetRegistration[]` | Gets all registered external widgets across all modules. |
@@ -6,6 +6,12 @@ Integrates an AI assistant panel (chatbot iframe) into the vc-shell application.
6
6
 
7
7
  The AI agent plugin embeds an external chatbot via an iframe panel that slides in from the right side of the application. It automatically sends the current blade context (user, active blade, selected items) to the chatbot and handles incoming commands (navigate, preview changes, download files). The plugin is optional -- if no `APP_AI_AGENT_URL` environment variable or `config.url` is provided, it silently skips installation.
8
8
 
9
+ ## When to Use
10
+
11
+ - Embed an AI assistant chatbot panel into your vc-shell application with automatic blade context passing
12
+ - Enable preview/apply workflows where AI suggests changes and the user confirms them
13
+ - When NOT to use: if you don't have an AI agent backend -- the plugin silently skips when no `APP_AI_AGENT_URL` is set
14
+
9
15
  ## Installation / Registration
10
16
 
11
17
  ```typescript
@@ -4,6 +4,12 @@ Extension points enable **cross-module UI composition** without direct imports.
4
4
 
5
5
  This is how vc-shell achieves its modular architecture: modules can extend each other without compile-time dependencies.
6
6
 
7
+ ## When to Use
8
+
9
+ - Module A needs to inject UI into Module B without a compile-time dependency (e.g., a "Reviews" tab inside a "Product Details" blade owned by another module)
10
+ - You need order-independent, runtime-resolved composition -- modules can load in any order
11
+ - When NOT to use: for simple parent-child communication within one module -- use props/slots/provide-inject instead; for data-only integration -- use events or a shared composable
12
+
7
13
  ---
8
14
 
9
15
  ## Table of Contents
@@ -8,6 +8,12 @@ In a complex admin shell with dynamically loaded modules, errors can originate f
8
8
 
9
9
  The global error handler solves this by installing three layers of error catching that cover all common error sources. Every caught error is parsed into a human-readable message and displayed as a toast notification, ensuring users always know when something goes wrong -- even if the module developer forgot to add error handling.
10
10
 
11
+ ## When to Use
12
+
13
+ - This plugin is always active -- it catches unhandled errors from Vue components, async rejections, and uncaught JS errors automatically
14
+ - You don't need to install or configure it -- the framework does this during app setup
15
+ - When to be aware: if you handle errors manually in a composable (try/catch), the global handler won't fire for those -- which is the desired behavior
16
+
11
17
  ### Three Layers of Error Catching
12
18
 
13
19
  1. **Vue `app.config.errorHandler`** -- catches errors thrown inside Vue components (render functions, lifecycle hooks, watchers, event handlers)
@@ -8,6 +8,12 @@ The vc-shell framework supports multiple languages through a centralized `vue-i1
8
8
 
9
9
  This approach ensures consistency across the application: switching the active locale updates all modules simultaneously, fallback behavior is uniform, and there is a single source of truth for the current language.
10
10
 
11
+ ## When to Use
12
+
13
+ - Add translated labels, messages, and error strings to any module (provide locale files via `defineAppModule({ locales })`)
14
+ - Access the current locale or switch languages at runtime via `useI18n()` or `useLanguages()`
15
+ - When NOT to use: this plugin is always active -- you never skip it. If you only need a single language, still use i18n keys for consistency
16
+
11
17
  Modules do not install this plugin directly. Instead, they provide their locale messages via `defineAppModule({ locales })`, and the framework merges them into the global i18n instance using `i18n.global.mergeLocaleMessage()` during module installation.
12
18
 
13
19
  ## Installation / Registration
@@ -175,6 +181,74 @@ import { i18n } from "@vc-shell/framework";
175
181
  const message = i18n.global.t("MY_MODULE.SOME_KEY");
176
182
  ```
177
183
 
184
+ ## Customizing Framework Translations
185
+
186
+ The framework exports its locale files as typed ES modules. App developers can import them to create full translations or override specific keys.
187
+
188
+ ### Importing Framework Locales
189
+
190
+ ```typescript
191
+ import en from '@vc-shell/framework/locales/en'
192
+ import de from '@vc-shell/framework/locales/de'
193
+ import type { VcShellLocale, VcShellLocalePartial } from '@vc-shell/framework/locales/types'
194
+ ```
195
+
196
+ The `VcShellLocale` interface provides autocomplete for all framework translation keys. Use it when creating a full translation to ensure no keys are missed.
197
+
198
+ ### Creating a New Language
199
+
200
+ Spread the English locale as a base and override all sections:
201
+
202
+ ```typescript
203
+ import en from '@vc-shell/framework/locales/en'
204
+ import type { VcShellLocale } from '@vc-shell/framework/locales/types'
205
+
206
+ const fr: VcShellLocale = {
207
+ ...en,
208
+ CORE: { THEMES: { LIGHT: "Clair", DARK: "Sombre" } },
209
+ SHELL: {
210
+ ...en.SHELL,
211
+ ACCOUNT: {
212
+ SETTINGS: "Paramètres",
213
+ CHANGE_PASSWORD: "Changer le mot de passe",
214
+ PROFILE: "Profil",
215
+ LOGOUT: "Déconnexion",
216
+ },
217
+ },
218
+ COMPONENTS: {
219
+ ...en.COMPONENTS,
220
+ // ... translate component strings
221
+ },
222
+ }
223
+
224
+ // Register in your app's main.ts or plugin setup:
225
+ app.config.globalProperties.$mergeLocaleMessage('fr', fr)
226
+ ```
227
+
228
+ ### Overriding Specific Keys
229
+
230
+ Use `VcShellLocalePartial` for partial overrides — all keys are optional:
231
+
232
+ ```typescript
233
+ import type { VcShellLocalePartial } from '@vc-shell/framework/locales/types'
234
+
235
+ const overrides: VcShellLocalePartial = {
236
+ SHELL: { ACCOUNT: { LOGOUT: "Sign Out" } }
237
+ }
238
+
239
+ app.config.globalProperties.$mergeLocaleMessage('en', overrides)
240
+ ```
241
+
242
+ ### Validating Your Translation
243
+
244
+ Run the CLI tool to check for missing or extra keys:
245
+
246
+ ```bash
247
+ npx vc-check-locales ./src/locales/fr.json
248
+ ```
249
+
250
+ In development mode, the framework also logs console warnings when a translation key is accessed but has no value for the current locale.
251
+
178
252
  ## Related
179
253
 
180
254
  - `framework/core/plugins/modularity/index.ts` -- `defineAppModule` merges locale messages via `i18n.global.mergeLocaleMessage()`
@@ -4,6 +4,12 @@ The modularity plugin is the backbone of every vc-shell application. It defines
4
4
 
5
5
  If you are building anything in vc-shell -- a new page, a CRUD flow, a dashboard widget, a notification handler -- you will start by creating a module.
6
6
 
7
+ ## When to Use
8
+
9
+ - Package any feature as a self-contained unit with blades, menu items, locales, notifications, and permissions
10
+ - Every vc-shell feature starts here -- `defineAppModule()` is the entry point for all module development
11
+ - When NOT to use: for shared utilities or composables that have no UI -- export them as plain TypeScript modules instead
12
+
7
13
  ---
8
14
 
9
15
  ## Table of Contents
@@ -8,6 +8,12 @@ The VirtoCommerce platform uses a role-based permission system where each user i
8
8
 
9
9
  The plugin installs the `hasAccess()` function from the `usePermissions()` composable as both a Vue global property (`$hasAccess`) and a provide/inject injectable (`"$hasAccess"`). This means permission checks are available everywhere: in templates via `$hasAccess()`, in composition API code via `usePermissions()`, and in any injecting component.
10
10
 
11
+ ## When to Use
12
+
13
+ - Show/hide UI elements based on user permissions: `v-if="$hasAccess('order:create')"` in templates or `hasAccess()` in composables
14
+ - Guard toolbar buttons, menu items, or entire blade sections with client-side permission checks
15
+ - When NOT to use: as the sole authorization layer -- always enforce permissions server-side too; for authentication checks (logged in / logged out) -- use `useUser` instead
16
+
11
17
  The framework installs this plugin automatically during app setup. Module developers never need to register it manually.
12
18
 
13
19
  ## Installation / Registration
@@ -4,6 +4,12 @@ Real-time push notification transport via ASP.NET SignalR. Connects to the platf
4
4
 
5
5
  ## Overview
6
6
 
7
+ ## When to Use
8
+
9
+ - Receive real-time push notifications from the platform (order updates, export progress, background job status)
10
+ - Display live notification toasts and badge counts without polling
11
+ - When NOT to use: for request-response API calls -- use `useApiClient`; for periodic data refresh -- use polling with `useAsync` instead
12
+
7
13
  The VirtoCommerce platform uses ASP.NET SignalR to push real-time notifications to connected clients. These notifications cover events like order status changes, catalog exports completing, background job progress, and system alerts.
8
14
 
9
15
  The SignalR plugin establishes a persistent WebSocket connection to the platform hub and listens for two event channels:
@@ -8,6 +8,12 @@ Form validation is a critical part of any admin interface. The vc-shell framewor
8
8
 
9
9
  This plugin auto-registers every rule from `@vee-validate/rules` (required, email, min, max, etc.) via `defineRule()`, then adds custom rules specific to vc-shell use cases. Once registered, rules are available globally in any `<Field>` component, `useField()` composable, or validation schema -- no per-component imports needed.
10
10
 
11
+ ## When to Use
12
+
13
+ - Validate form fields in blades using `<Field>` components or `useField()` with declarative rules (`required`, `email`, `min`, etc.)
14
+ - Use custom vc-shell rules: image dimensions (`validateImageMinSize`), file weight, date comparisons, BigInt safety
15
+ - When NOT to use: for API-level validation -- that belongs server-side; for simple presence checks on non-form data -- use plain conditionals
16
+
11
17
  The framework imports this module during setup. No manual installation is needed by module developers.
12
18
 
13
19
  ## Installation / Registration
@@ -62,7 +62,7 @@ This centralized approach has several advantages:
62
62
 
63
63
  | Key | Type | Description |
64
64
  |-----|------|-------------|
65
- | `WidgetIdKey` | `string` | Widget identity (provided by WidgetProvider) |
65
+ | `WidgetScopeKey` | `IWidgetScope` | Widget scope (provided by WidgetContainer for component-based widgets via `WidgetScope.vue`) |
66
66
  | `AppRootElementKey` | `Ref<HTMLElement \| undefined>` | App root element for scoped Teleport |
67
67
  | `EmbeddedModeKey` | `boolean` | Whether the app runs in embedded mode |
68
68
  | `ShellIndicatorsKey` | `ComputedRef<boolean>` | Unread indicator state for sidebar |
@@ -4,7 +4,7 @@ A built-in blade module for managing file assets (images, documents, archives).
4
4
 
5
5
  ## Overview
6
6
 
7
- The Assets Manager is registered as an app module via `defineAppModule` and opened as a child blade from any parent blade that needs asset management. The parent provides handler callbacks for upload, edit, and remove operations -- the module handles the UI and delegates data mutations back to the parent.
7
+ The Assets Manager is registered as an app module via `defineAppModule` and opened as a child blade from any parent blade that needs asset management. The parent creates a `useAssetsManager` instance and passes it through `options.manager` -- the module handles the UI and delegates data mutations through the manager.
8
8
 
9
9
  ## Module Registration
10
10
 
@@ -17,56 +17,51 @@ import { AssetsManagerModule } from "@vc-shell/framework";
17
17
 
18
18
  The module exports `AssetsManagerModule` (a Vue plugin) and the `AssetsManager` component, which is declared as a global component.
19
19
 
20
- ## Props (Options)
20
+ ## Options (via `useBlade`)
21
21
 
22
- The blade receives its configuration via the `options` prop when opened:
22
+ The blade reads its configuration from `options` via `useBlade<AssetsManagerOptions>()` (not props):
23
23
 
24
24
  | Option | Type | Description |
25
25
  |---|---|---|
26
- | `title` | `string` | Custom blade title (defaults to i18n `ASSETS_MANAGER.TITLE`) |
27
- | `assets` | `ICommonAsset[]` | Initial array of assets to display |
28
- | `loading` | `Ref<boolean>` | Reactive loading state for the table overlay |
29
- | `assetsEditHandler` | `(assets) => ICommonAsset[]` | Called when assets are reordered or edited |
30
- | `assetsUploadHandler` | `(files) => Promise<ICommonAsset[]>` | Called with selected files for upload |
31
- | `assetsRemoveHandler` | `(assets) => Promise<ICommonAsset[]>` | Called to delete selected assets |
32
- | `disabled` | `boolean` | When true, hides upload/delete actions and disables reordering |
33
- | `hiddenFields` | `string[]` | Fields to hide in the detail view |
26
+ | `title` | `string?` | Custom blade title (defaults to i18n `ASSETS_MANAGER.TITLE`) |
27
+ | `manager` | `UseAssetsManagerReturn` | The asset manager instance from `useAssetsManager()`. **Must be wrapped in `markRaw()`** |
28
+ | `disabled` | `boolean?` | When true, hides upload/delete actions and disables reordering |
29
+ | `hiddenFields` | `string[]?` | Fields to hide in the detail view |
30
+
31
+ > **Breaking change:** The old options (`assets`, `loading`, `assetsUploadHandler`, `assetsEditHandler`, `assetsRemoveHandler`) have been removed. Pass a single `manager` instance instead. See [migration guide #32](../../../migration/32-use-assets-manager.md).
34
32
 
35
33
  ## Usage
36
34
 
37
35
  Open the Assets Manager as a child blade:
38
36
 
39
37
  ```typescript
40
- import { useBladeNavigation } from "@vc-shell/framework";
38
+ import { markRaw } from "vue";
39
+ import { useBlade, useAssetsManager } from "@vc-shell/framework";
40
+
41
+ const { openBlade } = useBlade();
41
42
 
42
- const { openBlade, resolveBladeByName } = useBladeNavigation();
43
+ const assetsManager = useAssetsManager(product.assets, {
44
+ uploadPath: () => `/catalog/${product.id}`,
45
+ confirmRemove: () => confirm("Delete selected assets?"),
46
+ });
43
47
 
44
48
  openBlade({
45
- blade: resolveBladeByName("AssetsManager"),
49
+ name: "AssetsManager",
46
50
  options: {
47
- assets: product.assets,
48
- loading: isLoading,
51
+ // IMPORTANT: markRaw prevents Vue from wrapping the manager in a
52
+ // deep reactive proxy. Without it, blade options (stored in
53
+ // ref<BladeDescriptor[]>) auto-unwrap nested Refs, breaking
54
+ // manager.items.value and manager.loading.value access.
55
+ manager: markRaw(assetsManager),
49
56
  disabled: !canEdit.value,
50
57
  hiddenFields: ["sortOrder"],
51
- assetsUploadHandler: async (files) => {
52
- const uploaded = await uploadFiles(files);
53
- return [...product.assets, ...uploaded];
54
- },
55
- assetsEditHandler: (assets) => {
56
- product.assets = assets;
57
- return assets;
58
- },
59
- assetsRemoveHandler: async (assets) => {
60
- await deleteAssets(assets);
61
- return product.assets.filter(a => !assets.includes(a));
62
- },
63
58
  },
64
59
  });
65
60
  ```
66
61
 
67
62
  ## Features
68
63
 
69
- - **Table view**: Displays assets with thumbnail, name, size, sort order, and creation date columns.
64
+ - **Table view**: Displays assets with thumbnail, name, size, sort order, and creation date columns using `VcDataTable` with declarative `<VcColumn>` API.
70
65
  - **Upload**: Via toolbar button or drag-and-drop onto the table. Handles duplicate filenames by appending `_1`, `_2`, etc.
71
66
  - **Delete**: Toolbar delete button (requires selection) and per-row action button.
72
67
  - **Reorder**: Drag-and-drop row reordering when not in readonly mode.
@@ -79,17 +74,18 @@ openBlade({
79
74
  | Button | Condition | Action |
80
75
  |---|---|---|
81
76
  | Add | `!disabled` | Opens file picker for upload |
82
- | Delete | `!disabled` and items selected | Calls `assetsRemoveHandler` with selected items |
77
+ | Delete | `!disabled` and items selected | Calls `manager.removeMany()` with selected items |
83
78
 
84
79
  ## Tips
85
80
 
86
- - All mutation handlers must return the updated full asset array -- the module replaces its internal list with the return value.
81
+ - **Always use `markRaw()` when passing manager through blade options.** Blade descriptors are stored in `ref<BladeDescriptor[]>()`, which creates a deep reactive proxy. Vue auto-unwraps `Ref` values inside reactive objects, so `manager.items` would become a plain array instead of `Ref<AssetLike[]>` breaking `.value` access. `markRaw()` prevents this by telling Vue not to make the manager reactive.
82
+ - The `manager` object (from `useAssetsManager()`) owns the reactive asset list and all mutation methods. The blade reads `manager.items` and calls `manager.upload()`, `manager.remove()`, `manager.removeMany()`, `manager.reorder()`, and `manager.updateItem()`.
87
83
  - The `disabled` prop makes the entire blade readonly: no upload, no delete, no reorder.
88
84
  - Asset thumbnails use `isImage()` from shared utilities to determine if an image preview or a file-type icon should be shown.
89
- - The module uses `useBladeNavigation()` internally to open the detail sub-blade.
85
+ - The module uses `useBlade()` internally to open the detail sub-blade.
90
86
 
91
87
  ## Related
92
88
 
89
+ - `framework/core/composables/useAssetsManager/` -- `useAssetsManager`, `AssetLike`, `UseAssetsManagerReturn`
93
90
  - `framework/shared/utilities/assets.ts` -- `isImage`, `getFileThumbnail`, `readableSize`
94
91
  - `framework/core/utilities/date/` -- `formatDateRelative` for creation dates
95
- - `framework/core/types/` -- `ICommonAsset` interface
@@ -1,6 +1,6 @@
1
1
  # LogoutButton
2
2
 
3
- Settings menu action that signs the user out, closes all open blades, and redirects to the login page. This component encapsulates the full sign-out flow: it injects `CloseSettingsMenuKey` to dismiss the parent menu before navigation, calls `useUserManagement().signOut()` to clear the authentication token, and uses `useBladeNavigation()` to close any open blades so the user does not return to stale blade state on next login.
3
+ Settings menu action that signs the user out, closes all open blades, and redirects to the login page. This component encapsulates the full sign-out flow: it injects `CloseSettingsMenuKey` to dismiss the parent menu before navigation, calls `useUserManagement().signOut()` to clear the authentication token, and uses `useBladeStack()` to close open child blades so the user does not return to stale blade state on next login.
4
4
 
5
5
  ## When to Use
6
6
 
@@ -37,7 +37,7 @@ settingsMenu.register({
37
37
 
38
38
  ## Key Props
39
39
 
40
- This component has no props. It uses `useUserManagement()` for sign-out and `useBladeNavigation()` to close blades.
40
+ This component has no props. It uses `useUserManagement()` for sign-out and `useBladeStack()` to close blades.
41
41
 
42
42
  ## Recipe: Module with Complete Settings Menu Registration
43
43
 
@@ -71,7 +71,7 @@ export default {
71
71
  ## Details
72
72
 
73
73
  - **Menu dismissal**: Before starting the sign-out flow, the component calls the injected `CloseSettingsMenuKey` function to close the dropdown. This prevents a visual artifact where the menu stays open while the page redirects.
74
- - **Blade cleanup**: All open blades are closed via `useBladeNavigation()` before redirecting. This avoids orphaned blade state in the navigation stack.
74
+ - **Blade cleanup**: All open child blades are closed via `useBladeStack()` before redirecting. This avoids orphaned blade state in the navigation stack.
75
75
  - **Redirect**: After sign-out completes, the user is redirected to the `/login` route.
76
76
  - **External providers**: If the user signed in through an external SSO provider, the sign-out flow also triggers the provider's logout redirect via `useExternalProvider().signOut()`.
77
77
 
@@ -2,6 +2,17 @@
2
2
 
3
3
  A versatile button component supporting 9 style variants, 5 size options, loading state, and icon integration. VcButton is the primary interactive element for triggering actions throughout the application -- from toolbar buttons and form submissions to inline table actions and navigation.
4
4
 
5
+ ## When to Use
6
+
7
+ | Scenario | Component |
8
+ |----------|-----------|
9
+ | Triggering an action (save, delete, submit) | **VcButton** |
10
+ | Navigating to another URL or route | `<router-link>` or `<a>` tag |
11
+ | Action inside a blade toolbar | `IBladeToolbar` items (rendered as buttons automatically) |
12
+ | Inline text-style link within a paragraph | Native `<a>` or `<router-link>` |
13
+
14
+ Use VcButton for any user-initiated action that does not navigate to a URL. **Do not use** VcButton for navigation -- the `link` variant looks like a link but calls `preventDefault()`, so actual URL navigation will not work. For toolbar actions, define `IBladeToolbar` objects instead of placing VcButton manually.
15
+
5
16
  ## Quick Start
6
17
 
7
18
  ```vue