@vc-shell/vc-app-skill 2.0.5 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +7 -7
  3. package/package.json +1 -1
  4. package/runtime/VERSION +1 -1
  5. package/runtime/knowledge/docs/_BUILD_HASH.md +1 -1
  6. package/runtime/knowledge/docs/core/api/platform.docs.md +6 -6
  7. package/runtime/knowledge/docs/core/blade-navigation/blade-nav-composables.docs.md +1 -1
  8. package/runtime/knowledge/docs/core/composables/bladeContext/index.docs.md +2 -2
  9. package/runtime/knowledge/docs/core/composables/useApiClient/useApiClient.docs.md +67 -42
  10. package/runtime/knowledge/docs/core/composables/useAppBarMobileButtons/useAppBarMobileButtons.docs.md +2 -2
  11. package/runtime/knowledge/docs/core/composables/useAppBarWidget/useAppBarWidget.docs.md +15 -13
  12. package/runtime/knowledge/docs/core/composables/useAppInsights/useAppInsights.docs.md +11 -11
  13. package/runtime/knowledge/docs/core/composables/useAsync/useAsync.docs.md +17 -14
  14. package/runtime/knowledge/docs/core/composables/useBeforeUnload/useBeforeUnload.docs.md +4 -2
  15. package/runtime/knowledge/docs/core/composables/useBlade/useBlade.docs.md +31 -23
  16. package/runtime/knowledge/docs/core/composables/useBladeWidgets/index.docs.md +46 -26
  17. package/runtime/knowledge/docs/core/composables/useDashboard/useDashboard.docs.md +1 -0
  18. package/runtime/knowledge/docs/core/composables/useDynamicProperties/useDynamicProperties.docs.md +1 -1
  19. package/runtime/knowledge/docs/core/composables/useMenuExpanded/index.docs.md +1 -1
  20. package/runtime/knowledge/docs/core/composables/useMenuService/useMenuService.docs.md +1 -0
  21. package/runtime/knowledge/docs/core/composables/useNotifications/useNotifications.docs.md +20 -6
  22. package/runtime/knowledge/docs/core/composables/usePermissions/usePermissions.docs.md +2 -1
  23. package/runtime/knowledge/docs/core/composables/useSettings/useSettings.docs.md +2 -2
  24. package/runtime/knowledge/docs/core/composables/useToolbar/useToolbar.docs.md +109 -133
  25. package/runtime/knowledge/docs/core/composables/useWidgets/useWidgets.docs.md +1 -2
  26. package/runtime/knowledge/docs/core/notifications/composables/useBladeNotifications.docs.md +184 -0
  27. package/runtime/knowledge/docs/core/notifications/composables/useBroadcastFilter.docs.md +117 -0
  28. package/runtime/knowledge/docs/core/notifications/composables/useNotificationContext.docs.md +150 -0
  29. package/runtime/knowledge/docs/core/notifications/composables/useNotificationStore.docs.md +113 -0
  30. package/runtime/knowledge/docs/core/notifications/notifications.docs.md +12 -15
  31. package/runtime/knowledge/docs/core/plugins/extension-points/extension-points.docs.md +1 -1
  32. package/runtime/knowledge/docs/core/plugins/modularity/modularity.docs.md +54 -116
  33. package/runtime/knowledge/docs/core/plugins/permissions/permissions.docs.md +12 -3
  34. package/runtime/knowledge/docs/core/plugins/signalR/signalR.docs.md +20 -20
  35. package/runtime/knowledge/docs/injection-keys.docs.md +1 -1
  36. package/runtime/knowledge/docs/shell/auth/LoginPage/login-page.docs.md +1 -1
  37. package/runtime/knowledge/docs/shell/components/language-selector/language-selector.docs.md +1 -1
  38. package/runtime/knowledge/docs/shell/components/notification-dropdown/notification-dropdown.docs.md +1 -1
  39. package/runtime/knowledge/docs/shell/components/notification-template/notification-template.docs.md +1 -1
  40. package/runtime/knowledge/docs/shell/components/settings-menu/settings-menu.docs.md +7 -7
  41. package/runtime/knowledge/docs/shell/components/settings-menu-item/settings-menu-item.docs.md +2 -2
  42. package/runtime/knowledge/docs/shell/dashboard/draggable-dashboard/draggable-dashboard.docs.md +1 -1
  43. package/runtime/knowledge/docs/ui/components/atoms/vc-badge/vc-badge.docs.md +4 -4
  44. package/runtime/knowledge/docs/ui/components/atoms/vc-button/vc-button.docs.md +1 -1
  45. package/runtime/knowledge/docs/ui/components/atoms/vc-container/vc-container.docs.md +21 -1
  46. package/runtime/knowledge/docs/ui/components/atoms/vc-link/vc-link.docs.md +22 -6
  47. package/runtime/knowledge/docs/ui/components/atoms/vc-skeleton/vc-skeleton.docs.md +2 -2
  48. package/runtime/knowledge/docs/ui/components/atoms/vc-status-icon/vc-status-icon.docs.md +32 -26
  49. package/runtime/knowledge/docs/ui/components/atoms/vc-tooltip/vc-tooltip.docs.md +3 -3
  50. package/runtime/knowledge/docs/ui/components/molecules/multilanguage-selector/multilanguage-selector.docs.md +1 -1
  51. package/runtime/knowledge/docs/ui/components/molecules/vc-accordion/vc-accordion.docs.md +1 -1
  52. package/runtime/knowledge/docs/ui/components/molecules/vc-breadcrumbs/vc-breadcrumbs.docs.md +1 -1
  53. package/runtime/knowledge/docs/ui/components/molecules/vc-dropdown-panel/vc-dropdown-panel.docs.md +13 -13
  54. package/runtime/knowledge/docs/ui/components/molecules/vc-form/vc-form.docs.md +1 -1
  55. package/runtime/knowledge/docs/ui/components/molecules/vc-pagination/vc-pagination.docs.md +1 -1
  56. package/runtime/knowledge/docs/ui/components/organisms/vc-app/vc-app.docs.md +4 -4
  57. package/runtime/knowledge/docs/ui/components/organisms/vc-blade/vc-blade.docs.md +39 -37
  58. package/runtime/knowledge/docs/ui/components/organisms/vc-data-table/vc-data-table.docs.md +17 -17
  59. package/runtime/knowledge/docs/ui/components/organisms/vc-dynamic-property/vc-dynamic-property.docs.md +16 -7
  60. package/runtime/knowledge/docs/ui/components/organisms/vc-sidebar/vc-sidebar.docs.md +1 -1
@@ -5,84 +5,88 @@ group: services
5
5
  ---
6
6
 
7
7
  !!! tip "Long page"
8
- Use the section headings to jump directly to what you need: [Quick Start](#quick-start), [Updating Button State Dynamically](#updating-button-state-dynamically), [Visibility and Permissions](#visibility-and-permissions), or [API Reference](#api-reference).
8
+ Use the section headings to jump directly to what you need: [Primary pattern: array + toolbar-items](#primary-pattern-array--toolbar-items), [When to reach for useToolbar](#when-to-reach-for-usetoolbar), [Quick Start](#quick-start), or [API Reference](#api-reference).
9
9
 
10
10
  # useToolbar
11
11
 
12
- 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.
12
+ `useToolbar` is the framework's **advanced** API for blade toolbar registration. The everyday way to give a blade a toolbar is the `:toolbar-items` prop on `VcBlade` with a plain `IBladeToolbar[]` array — there is no need to call `useToolbar` in the typical case. Reach for `useToolbar` when toolbar items must appear after mount, when a blade needs to mutate another blade's toolbar, or when toolbar composition is driven from a non-blade owner.
13
13
 
14
- ## When to Use
14
+ ## Primary pattern: array + toolbar-items
15
15
 
16
- - Add action buttons (Save, Delete, Refresh, Export) to a blade's toolbar header
17
- - Dynamically update button state (disabled, visible, icon) in response to loading or form changes
18
- - When NOT to use: for global app-bar actions -- use `useAppBarWidget`; for navigation links -- use `useMenuService`
16
+ In a regular blade, declare the toolbar as `ref<IBladeToolbar[]>([...])` and bind it. Reactive fields (`isVisible`, `disabled`, computed `title`) drive visibility and state without any `watch` plumbing. The framework filters items by `permissions` and `isVisible` before render.
19
17
 
20
- ## Quick Start
21
-
22
- ```typescript
18
+ ```vue title="orders-details.vue"
23
19
  <script setup lang="ts">
24
- import { useToolbar } from "@vc-shell/framework";
20
+ import { computed, ref } from "vue";
21
+ import { useAsync, usePermissions, VcBlade, type IBladeToolbar } from "@vc-shell/framework";
25
22
 
26
- const { registerToolbarItem } = useToolbar();
27
-
28
- registerToolbarItem({
29
- id: "save",
30
- title: "Save",
31
- icon: "fas fa-save",
32
- clickHandler: () => saveData(),
23
+ const { hasAccess } = usePermissions();
24
+ const { loading: saving, action: save } = useAsync(async () => {
25
+ await saveOrder(order.value);
33
26
  });
27
+
28
+ const bladeToolbar = ref<IBladeToolbar[]>([
29
+ {
30
+ id: "save",
31
+ title: "Save",
32
+ icon: "lucide-save",
33
+ disabled: computed(() => saving.value),
34
+ isVisible: computed(() => isDirty.value && hasAccess("order:update")),
35
+ clickHandler: () => save(),
36
+ },
37
+ {
38
+ id: "delete",
39
+ title: "Delete",
40
+ icon: "lucide-trash",
41
+ isVisible: computed(() => hasAccess(["order:delete", "order:manage"])),
42
+ clickHandler: () => confirmDelete(),
43
+ },
44
+ ]);
34
45
  </script>
46
+
47
+ <template>
48
+ <VcBlade :toolbar-items="bladeToolbar" />
49
+ </template>
35
50
  ```
36
51
 
37
- The button appears in the current blade's toolbar immediately. When the component unmounts, the button is automatically removed.
52
+ Use this pattern for every blade unless you have a concrete reason to register imperatively. `IBladeToolbar` is exported from `@vc-shell/framework`; see [Core types](../../types/) for the full shape. Permission gating goes through `isVisible: computed(() => hasAccess(...))` so it stays reactive when the user's permission set changes.
38
53
 
39
- ## Registering Toolbar Buttons
54
+ ## When to reach for useToolbar
40
55
 
41
- Every toolbar button requires a unique `id`. The remaining properties control appearance, behavior, and ordering.
56
+ `useToolbar` becomes the right tool only in these cases:
42
57
 
43
- ```typescript
44
- <script setup lang="ts">
58
+ - **Dynamic registration after mount.** A toolbar item is decided after the blade is already on screen — for example, a button that appears after a long-running operation completes and never has a representation in the initial array.
59
+ - **Cross-blade toolbar mutation.** Code in blade A needs to add or change a button on blade B. Pass `targetBladeId` to the imperative methods.
60
+ - **Non-blade owner.** A composable or service that doesn't live inside a blade `<script setup>` but still needs to contribute toolbar items.
61
+ - **Wildcard / global items.** Toolbar items that appear on every blade through the `*` wildcard bladeId.
62
+
63
+ In all other cases, declare the array and bind `:toolbar-items`. The imperative API exists to cover edge cases, not to replace the array.
64
+
65
+ ## Quick Start
66
+
67
+ Imperative registration from inside a blade, scoped to the current blade context:
68
+
69
+ ```typescript title="<script setup>"
45
70
  import { useToolbar } from "@vc-shell/framework";
46
71
 
47
72
  const { registerToolbarItem } = useToolbar();
48
73
 
49
- // Primary action — highest priority, appears first
50
74
  registerToolbarItem({
51
75
  id: "save",
52
76
  title: "Save",
53
77
  icon: "fas fa-save",
54
- clickHandler: () => save(),
55
- priority: 100,
56
- });
57
-
58
- // Secondary action — lower priority, appears after Save
59
- registerToolbarItem({
60
- id: "refresh",
61
- title: "Refresh",
62
- icon: "fas fa-sync",
63
- clickHandler: () => refresh(),
64
- priority: 50,
65
- });
66
-
67
- // Destructive action with permission gate
68
- registerToolbarItem({
69
- id: "delete",
70
- title: "Delete",
71
- icon: "fas fa-trash",
72
- clickHandler: () => confirmDelete(),
73
- priority: 10,
74
- permissions: "order:delete",
78
+ clickHandler: () => saveData(),
75
79
  });
76
- </script>
77
80
  ```
78
81
 
79
- > **Note:** The `priority` field controls display order. Higher values appear first (leftmost). The default is `0`.
82
+ The button appears in the current blade's toolbar immediately. When the component unmounts, the button is automatically removed (controlled by `autoCleanup`).
80
83
 
81
- ## Updating Button State Dynamically
84
+ ## Updating button state dynamically
82
85
 
83
- Use `updateToolbarItem` to change any property of a registered button without re-registering it. This is the recommended pattern for toggling disabled state during async operations.
86
+ Use `updateToolbarItem` to change any property of a registered button without re-registering it. This is the imperative analogue of binding a reactive `disabled` value in the array pattern.
84
87
 
85
88
  ```typescript
89
+ // pseudo-code: replace OrderClient with your generated API client
86
90
  <script setup lang="ts">
87
91
  import { watch } from "vue";
88
92
  import { useToolbar, useAsync } from "@vc-shell/framework";
@@ -109,6 +113,8 @@ watch(loading, (isLoading) => {
109
113
  </script>
110
114
  ```
111
115
 
116
+ In the array pattern, the same outcome reads as `disabled: computed(() => loading.value)` on the entry — no `watch` needed. Use `updateToolbarItem` only when the item has been registered imperatively and there is no array reference to mutate.
117
+
112
118
  You can update any subset of `IToolbarItem` properties:
113
119
 
114
120
  ```typescript
@@ -119,14 +125,12 @@ updateToolbarItem("toggle-publish", {
119
125
  });
120
126
  ```
121
127
 
122
- ## Visibility and Permissions
128
+ ## Visibility and permissions
123
129
 
124
- Toolbar items support both reactive visibility and permission-based access control.
130
+ Both reactive visibility and permission gating work the same way whether the item lives in an array or is registered imperatively — the framework filters by `isVisible` and `permissions` before render in either path.
125
131
 
126
132
  ### Reactive visibility with `isVisible`
127
133
 
128
- The `isVisible` property accepts a boolean, a `Ref<boolean>`, a `ComputedRef<boolean>`, or a function:
129
-
130
134
  ```typescript
131
135
  import { computed } from "vue";
132
136
 
@@ -143,25 +147,7 @@ registerToolbarItem({
143
147
 
144
148
  ### Permission-based registration
145
149
 
146
- Use `usePermissions` alongside `useToolbar` to conditionally register buttons:
147
-
148
- ```typescript
149
- import { useToolbar, usePermissions } from "@vc-shell/framework";
150
-
151
- const { registerToolbarItem } = useToolbar();
152
- const { hasAccess } = usePermissions();
153
-
154
- if (hasAccess("order:delete")) {
155
- registerToolbarItem({
156
- id: "delete",
157
- title: "Delete",
158
- icon: "fas fa-trash",
159
- clickHandler: () => deleteOrder(),
160
- });
161
- }
162
- ```
163
-
164
- Alternatively, set the `permissions` property on the item itself. The toolbar renderer checks this before displaying the button:
150
+ Set `permissions` on the item; the renderer hides it when the user lacks the listed permission(s) (OR logic on arrays):
165
151
 
166
152
  ```typescript
167
153
  registerToolbarItem({
@@ -169,13 +155,15 @@ registerToolbarItem({
169
155
  title: "Delete",
170
156
  icon: "fas fa-trash",
171
157
  clickHandler: () => deleteOrder(),
172
- permissions: ["order:delete"],
158
+ permissions: ["order:delete", "order:manage"],
173
159
  });
174
160
  ```
175
161
 
176
- ## Cross-Blade Toolbar Management
162
+ Avoid wrapping `registerToolbarItem` in an `if (hasAccess(...))` check unless you specifically want to skip side effects of the registration; the `permissions` field already gates rendering.
163
+
164
+ ## Cross-blade toolbar management
177
165
 
178
- By default, all methods operate on the current blade. Pass an explicit `targetBladeId` to manage another blade's toolbar:
166
+ All methods accept an optional `targetBladeId` second argument. Pass it to register, update, or clear toolbar items on a blade other than the current one:
179
167
 
180
168
  ```typescript
181
169
  // Register a button on a specific child blade
@@ -185,14 +173,15 @@ registerToolbarItem({ id: "child-action", title: "Action", clickHandler: () => {
185
173
  clearBladeToolbarItems("ChildBlade");
186
174
  ```
187
175
 
188
- ## Auto-Cleanup Control
176
+ Pass `"*"` as the bladeId to register a global item that appears on every blade.
177
+
178
+ ## Auto-cleanup control
189
179
 
190
- By default, all toolbar items registered by a component are removed when that component unmounts (`autoCleanup: true`). Disable this for shared toolbar items that should persist:
180
+ By default, items registered through `useToolbar()` inside a component setup are cleared when that component unmounts (`autoCleanup: true`). Disable this for items that should persist beyond the registering component:
191
181
 
192
182
  ```typescript
193
183
  const { registerToolbarItem } = useToolbar({ autoCleanup: false });
194
184
 
195
- // These items survive the component's unmount cycle
196
185
  registerToolbarItem({
197
186
  id: "global-help",
198
187
  title: "Help",
@@ -201,68 +190,38 @@ registerToolbarItem({
201
190
  });
202
191
  ```
203
192
 
204
- ## Recipes
205
-
206
- ### Complete Blade with Save / Delete / Refresh
193
+ Use `autoCleanup: false` for shared toolbar items registered outside a blade lifecycle (for example, from a module's bootstrap).
207
194
 
208
- ```typescript
209
- <script setup lang="ts">
210
- import { watch } from "vue";
211
- import { useToolbar, useAsync, usePermissions, useApiClient } from "@vc-shell/framework";
212
- import { OrderClient } from "@api/orders";
195
+ ## Recipes
213
196
 
214
- const { registerToolbarItem, updateToolbarItem } = useToolbar();
215
- const { hasAccess } = usePermissions();
216
- const { getApiClient } = useApiClient(OrderClient);
197
+ ### Dynamic registration after a non-blade action
217
198
 
218
- const { loading: saving, action: save } = useAsync(async () => {
219
- const client = await getApiClient();
220
- await client.updateOrder(order.value);
221
- });
199
+ `useToolbar` shines when a button is decided by a non-blade flow — for example, after a long-running export completes, expose a "Download result" button on the originating blade:
222
200
 
223
- const { loading: refreshing, action: refresh } = useAsync(async () => {
224
- const client = await getApiClient();
225
- order.value = await client.getOrderById(props.param);
226
- });
201
+ ```ts
202
+ // pseudo-code: replace ExportClient with your generated API client
203
+ import { useToolbar, useBlade } from "@vc-shell/framework";
227
204
 
228
- registerToolbarItem({
229
- id: "save",
230
- title: "Save",
231
- icon: "fas fa-save",
232
- clickHandler: () => save(),
233
- priority: 100,
234
- });
205
+ const { registerToolbarItem, unregisterToolbarItem } = useToolbar();
206
+ const { id: bladeId } = useBlade();
235
207
 
236
- registerToolbarItem({
237
- id: "refresh",
238
- title: "Refresh",
239
- icon: "fas fa-sync",
240
- clickHandler: () => refresh(),
241
- priority: 50,
242
- });
208
+ async function startExport() {
209
+ const job = await startServerExport();
210
+ await waitForCompletion(job.id);
243
211
 
244
- if (hasAccess("order:delete")) {
245
- registerToolbarItem({
246
- id: "delete",
247
- title: "Delete",
248
- icon: "fas fa-trash",
249
- clickHandler: () => confirmDelete(),
250
- priority: 10,
251
- });
212
+ registerToolbarItem(
213
+ {
214
+ id: `download-${job.id}`,
215
+ title: "Download CSV",
216
+ icon: "lucide-download",
217
+ clickHandler: () => downloadResult(job.id),
218
+ },
219
+ bladeId.value,
220
+ );
252
221
  }
253
-
254
- // Disable all buttons while any operation is running
255
- watch([saving, refreshing], ([s, r]) => {
256
- const busy = s || r;
257
- updateToolbarItem("save", { disabled: busy });
258
- updateToolbarItem("refresh", { disabled: busy });
259
- });
260
- </script>
261
222
  ```
262
223
 
263
- ### Visual Separator Between Button Groups
264
-
265
- Use the `separator` property to add a vertical divider:
224
+ ### Visual separator between button groups
266
225
 
267
226
  ```typescript
268
227
  registerToolbarItem({
@@ -275,7 +234,21 @@ registerToolbarItem({
275
234
  });
276
235
  ```
277
236
 
278
- ## Common Mistakes
237
+ ## Common mistakes
238
+
239
+ ### Reaching for `useToolbar` before considering the array pattern
240
+
241
+ ```typescript
242
+ // Avoid in everyday blades — preferred pattern is the array + :toolbar-items
243
+ const { registerToolbarItem } = useToolbar();
244
+ registerToolbarItem({ id: "save", ... });
245
+
246
+ // Preferred: declarative array bound to VcBlade
247
+ const bladeToolbar = ref<IBladeToolbar[]>([{ id: "save", ... }]);
248
+ // <VcBlade :toolbar-items="bladeToolbar" />
249
+ ```
250
+
251
+ The array form is shorter, reactive without `watch`/`updateToolbarItem` plumbing, and easier to test.
279
252
 
280
253
  ### Forgetting the `id` property
281
254
 
@@ -299,7 +272,7 @@ registerToolbarItem({
299
272
  ### Re-registering instead of updating
300
273
 
301
274
  ```typescript
302
- // Wrong -- creates duplicate entries on each loading state change
275
+ // Wrong -- duplicates the entry on each loading state change
303
276
  watch(loading, (isLoading) => {
304
277
  registerToolbarItem({
305
278
  id: "save",
@@ -315,7 +288,7 @@ watch(loading, (isLoading) => {
315
288
  });
316
289
  ```
317
290
 
318
- ### Using useToolbar outside a component setup
291
+ ### Using `useToolbar` outside a component setup
319
292
 
320
293
  ```typescript
321
294
  // Wrong -- no component instance, autoCleanup will not work
@@ -324,7 +297,7 @@ function helperFunction() {
324
297
  registerToolbarItem({ id: "test", title: "Test" });
325
298
  }
326
299
 
327
- // Correct -- call in <script setup> or within setup()
300
+ // Correct -- call in <script setup> or within setup(), or pass autoCleanup: false
328
301
  ```
329
302
 
330
303
  ## API Reference
@@ -368,8 +341,11 @@ function helperFunction() {
368
341
  | `permissions` | `string \| string[]` | No | Required permission(s) to display the button |
369
342
  | `bladeId` | `string` | No | Target blade ID (auto-resolved from context) |
370
343
 
344
+ `IToolbarItem` is the shape consumed by `ToolbarService`. The blade-level array binding uses `IBladeToolbar` (see [Core types](../../types/)), a near-identical shape that the framework normalizes into `IToolbarItem` before render.
345
+
371
346
  ## Related
372
347
 
373
348
  - [useBlade](../useBlade/) -- blade context that toolbar items are scoped to
374
349
  - [usePermissions](../usePermissions/) -- conditionally register toolbar items based on permissions
375
350
  - [useAsync](../useAsync/) -- wraps async operations with loading state for disabling buttons
351
+ - `IBladeToolbar` in [Core types](../../types/) — the shape used by the `:toolbar-items` array binding
@@ -153,6 +153,5 @@ const allWidgets = computed(() => [...bladeWidgets.value, ...externalWidgets.val
153
153
 
154
154
  ## Related
155
155
 
156
- - [useWidget](../useWidget/useWidget.docs.md) -- widget-side composable for registering trigger contracts (badge, refresh)
157
- - [useBladeWidgets](../useBladeWidgets/) -- lifecycle-managed widget registration for blades (preferred API)
156
+ - [useBladeWidgets](../useBladeWidgets/index.docs.md) -- lifecycle-managed widget registration for blades (preferred API)
158
157
  - `framework/core/services/widget-service.ts` -- underlying service implementation
@@ -0,0 +1,184 @@
1
+ ---
2
+ title: useBladeNotifications
3
+ category: composables
4
+ group: notifications
5
+ ---
6
+
7
+ # useBladeNotifications
8
+
9
+ Subscribes a blade to one or more push-notification types from the platform's SignalR stream. The composable returns a reactive list of matching unread messages, an unread count, and a `markAsRead` action. The subscription is bound to the current effect scope, so it disappears the moment the blade closes — no manual unsubscribe.
10
+
11
+ This is the **Level 2** entry point in the notification system. Level 1 — `defineAppModule({ notifications })` — registers types globally with their toast configuration and is the always-on path. Level 2 layers blade-specific behavior on top of that: refresh a list, update a progress UI, mark a job complete.
12
+
13
+ ## When to use
14
+
15
+ - A list blade needs to refresh when an entity is created, updated, or deleted elsewhere.
16
+ - A long-running operation has a dedicated blade and the blade should update as `processedCount` / `errorCount` flow in.
17
+ - A blade wants to surface an inline "N new" badge for messages of a specific type.
18
+ - When NOT to use: app-wide toasts already come from the Level 1 module config — the blade does not need to subscribe just to show a toast. Reach for the blade subscription only when you also need to _react_ to the event in code.
19
+
20
+ ## Quick Start
21
+
22
+ ```ts
23
+ import { useBladeNotifications } from "@vc-shell/framework";
24
+
25
+ useBladeNotifications({
26
+ types: ["OfferDeletedDomainEvent"],
27
+ onMessage: () => reload(),
28
+ });
29
+ ```
30
+
31
+ That is the full recipe for "refresh this list when an offer is deleted somewhere in the app." The handler runs once per matching message; the framework cleans up the subscription when the blade unmounts.
32
+
33
+ ## API
34
+
35
+ ### Parameters
36
+
37
+ ```typescript
38
+ interface BladeNotificationOptions<T extends PushNotification = PushNotification> {
39
+ types: string[];
40
+ filter?: (msg: T) => boolean;
41
+ onMessage?: (msg: T) => void;
42
+ }
43
+ ```
44
+
45
+ | Field | Type | Required | Description |
46
+ | ----------- | --------------------- | -------- | ------------------------------------------------------------------------------------------------------- |
47
+ | `types` | `string[]` | Yes | Notification types to subscribe to. Must match the `notifyType` field on incoming messages. |
48
+ | `filter` | `(msg: T) => boolean` | No | Narrow the subscription further (for example, only events for the entity this blade is editing). |
49
+ | `onMessage` | `(msg: T) => void` | No | Callback fired once per matching message. Use it to refresh data, mark progress, or update local state. |
50
+
51
+ ### Returns
52
+
53
+ ```typescript
54
+ interface BladeNotificationReturn<T extends PushNotification = PushNotification> {
55
+ messages: ComputedRef<T[]>;
56
+ unreadCount: ComputedRef<number>;
57
+ markAsRead: (msg: T) => void;
58
+ }
59
+ ```
60
+
61
+ | Property | Type | Description |
62
+ | ------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------- |
63
+ | `messages` | `ComputedRef<T[]>` | Realtime messages matching `types` and `filter` that are still unread. Updates reactively as new messages arrive. |
64
+ | `unreadCount` | `ComputedRef<number>` | `messages.value.length`. Bind to a badge. |
65
+ | `markAsRead` | `(msg: T) => void` | Mark a specific message as read. Removes it from `messages` (and reduces the global unread badge in the bell dropdown). |
66
+
67
+ ## Typed payloads
68
+
69
+ Notification payloads often extend `PushNotification` with domain fields. Pass the type parameter so `onMessage` and `messages` are typed:
70
+
71
+ ```ts
72
+ import type { PushNotification } from "@vc-shell/framework";
73
+
74
+ interface ImportPushNotification extends PushNotification {
75
+ jobId: string;
76
+ profileId: string;
77
+ profileName?: string;
78
+ processedCount: number;
79
+ errorCount: number;
80
+ finished: boolean;
81
+ }
82
+
83
+ const { messages, markAsRead } = useBladeNotifications<ImportPushNotification>({
84
+ types: ["ImportPushNotification"],
85
+ onMessage: (message) => {
86
+ if (message.finished) {
87
+ reload();
88
+ markAsRead(message);
89
+ }
90
+ },
91
+ });
92
+ ```
93
+
94
+ ## Common patterns
95
+
96
+ ### Refresh a list on any matching event
97
+
98
+ ```ts
99
+ useBladeNotifications({
100
+ types: ["OfferDeletedDomainEvent"],
101
+ onMessage: () => reload(),
102
+ });
103
+ ```
104
+
105
+ Drop-in for a list blade that needs to stay in sync with deletions happening anywhere in the app.
106
+
107
+ ### Filter to the entity this blade owns
108
+
109
+ ```ts
110
+ useBladeNotifications<ImportPushNotification>({
111
+ types: ["ImportPushNotification"],
112
+ onMessage: (message) => {
113
+ if (message.profileId !== param.value) return; // not our job
114
+ if (!message.finished) updateProgress(message);
115
+ else finalizeImport(message);
116
+ },
117
+ });
118
+ ```
119
+
120
+ Two open import blades will both receive the stream; each one filters by its own `profileId` so they do not step on each other.
121
+
122
+ ### Drive a manual progress toast
123
+
124
+ When the platform sends progress updates for a long-running job, you may want to render one persistent toast that you update as messages arrive — instead of letting Level 1 spawn a new toast per event.
125
+
126
+ Set the Level 1 type to `silent` and drive the toast yourself:
127
+
128
+ ```ts title="src/modules/import/index.ts"
129
+ defineAppModule({
130
+ notifications: {
131
+ ImportPushNotification: { toast: { mode: "silent" } },
132
+ },
133
+ // ...
134
+ });
135
+ ```
136
+
137
+ ```ts title="pages/import-process.vue"
138
+ import { useBladeNotifications, notification } from "@vc-shell/framework";
139
+
140
+ let toastId: string | undefined;
141
+
142
+ useBladeNotifications<ImportPushNotification>({
143
+ types: ["ImportPushNotification"],
144
+ onMessage: (message) => {
145
+ const content = message.profileName ? `${message.profileName}: ${message.title}` : message.title;
146
+
147
+ if (!toastId) {
148
+ toastId = notification(content, { timeout: false });
149
+ } else if (!message.finished) {
150
+ notification.update(toastId, { content });
151
+ } else {
152
+ notification.update(toastId, {
153
+ content,
154
+ timeout: 5000,
155
+ type: message.errorCount ? "error" : "success",
156
+ onClose: () => (toastId = undefined),
157
+ });
158
+ }
159
+ },
160
+ });
161
+ ```
162
+
163
+ The `notification()` helper returns the toast id; `notification.update` mutates it in place. The bell-dropdown history still grows — `silent` only suppresses the auto-toast.
164
+
165
+ ## Lifecycle
166
+
167
+ `useBladeNotifications` calls `useNotificationStore().subscribe(...)` and registers `onScopeDispose(unsub)` against the current effect scope. Inside a Vue `setup()` (component or `<script setup>`) the scope is the component's; the subscription dies with the component.
168
+
169
+ If you call the composable from a manually managed `effectScope()`, the cleanup runs when that scope is stopped. Calling it outside any scope is a bug — the subscription would never be released.
170
+
171
+ ## Tips
172
+
173
+ - **Listen, do not declare.** `useBladeNotifications` does not register the notification type with the framework. Types must already be declared by some module via `defineAppModule({ notifications })`, otherwise nothing reaches `onMessage`.
174
+ - **`messages` shows only unread.** `markAsRead(msg)` removes a message from `messages` (and from the global unread count). The notification stays in history.
175
+ - **One subscription per call.** Calling `useBladeNotifications` multiple times in the same blade creates independent subscriptions. Combine handlers if you only need one.
176
+ - **Type strings are case-sensitive.** The string in `types` must exactly equal the `notifyType` field on incoming messages.
177
+
178
+ ## Related
179
+
180
+ - [useNotificationStore](./useNotificationStore.md) — direct store access for app-shell features (dropdown, badge).
181
+ - [useNotificationContext](./useNotificationContext.md) — read the current notification inside a custom template.
182
+ - [useBroadcastFilter](./useBroadcastFilter.md) — control which `SendSystemEvents` broadcasts reach the store.
183
+ - [Notifications concept page.](../../concepts/notifications.md)
184
+ - [Notifications plugin reference.](../../plugins/notifications.md)