@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.
- package/CHANGELOG.md +6 -0
- package/README.md +7 -7
- package/package.json +1 -1
- package/runtime/VERSION +1 -1
- package/runtime/knowledge/docs/_BUILD_HASH.md +1 -1
- package/runtime/knowledge/docs/core/api/platform.docs.md +6 -6
- package/runtime/knowledge/docs/core/blade-navigation/blade-nav-composables.docs.md +1 -1
- package/runtime/knowledge/docs/core/composables/bladeContext/index.docs.md +2 -2
- package/runtime/knowledge/docs/core/composables/useApiClient/useApiClient.docs.md +67 -42
- package/runtime/knowledge/docs/core/composables/useAppBarMobileButtons/useAppBarMobileButtons.docs.md +2 -2
- package/runtime/knowledge/docs/core/composables/useAppBarWidget/useAppBarWidget.docs.md +15 -13
- package/runtime/knowledge/docs/core/composables/useAppInsights/useAppInsights.docs.md +11 -11
- package/runtime/knowledge/docs/core/composables/useAsync/useAsync.docs.md +17 -14
- package/runtime/knowledge/docs/core/composables/useBeforeUnload/useBeforeUnload.docs.md +4 -2
- package/runtime/knowledge/docs/core/composables/useBlade/useBlade.docs.md +31 -23
- package/runtime/knowledge/docs/core/composables/useBladeWidgets/index.docs.md +46 -26
- package/runtime/knowledge/docs/core/composables/useDashboard/useDashboard.docs.md +1 -0
- package/runtime/knowledge/docs/core/composables/useDynamicProperties/useDynamicProperties.docs.md +1 -1
- package/runtime/knowledge/docs/core/composables/useMenuExpanded/index.docs.md +1 -1
- package/runtime/knowledge/docs/core/composables/useMenuService/useMenuService.docs.md +1 -0
- package/runtime/knowledge/docs/core/composables/useNotifications/useNotifications.docs.md +20 -6
- package/runtime/knowledge/docs/core/composables/usePermissions/usePermissions.docs.md +2 -1
- package/runtime/knowledge/docs/core/composables/useSettings/useSettings.docs.md +2 -2
- package/runtime/knowledge/docs/core/composables/useToolbar/useToolbar.docs.md +109 -133
- package/runtime/knowledge/docs/core/composables/useWidgets/useWidgets.docs.md +1 -2
- package/runtime/knowledge/docs/core/notifications/composables/useBladeNotifications.docs.md +184 -0
- package/runtime/knowledge/docs/core/notifications/composables/useBroadcastFilter.docs.md +117 -0
- package/runtime/knowledge/docs/core/notifications/composables/useNotificationContext.docs.md +150 -0
- package/runtime/knowledge/docs/core/notifications/composables/useNotificationStore.docs.md +113 -0
- package/runtime/knowledge/docs/core/notifications/notifications.docs.md +12 -15
- package/runtime/knowledge/docs/core/plugins/extension-points/extension-points.docs.md +1 -1
- package/runtime/knowledge/docs/core/plugins/modularity/modularity.docs.md +54 -116
- package/runtime/knowledge/docs/core/plugins/permissions/permissions.docs.md +12 -3
- package/runtime/knowledge/docs/core/plugins/signalR/signalR.docs.md +20 -20
- package/runtime/knowledge/docs/injection-keys.docs.md +1 -1
- package/runtime/knowledge/docs/shell/auth/LoginPage/login-page.docs.md +1 -1
- package/runtime/knowledge/docs/shell/components/language-selector/language-selector.docs.md +1 -1
- package/runtime/knowledge/docs/shell/components/notification-dropdown/notification-dropdown.docs.md +1 -1
- package/runtime/knowledge/docs/shell/components/notification-template/notification-template.docs.md +1 -1
- package/runtime/knowledge/docs/shell/components/settings-menu/settings-menu.docs.md +7 -7
- package/runtime/knowledge/docs/shell/components/settings-menu-item/settings-menu-item.docs.md +2 -2
- package/runtime/knowledge/docs/shell/dashboard/draggable-dashboard/draggable-dashboard.docs.md +1 -1
- package/runtime/knowledge/docs/ui/components/atoms/vc-badge/vc-badge.docs.md +4 -4
- package/runtime/knowledge/docs/ui/components/atoms/vc-button/vc-button.docs.md +1 -1
- package/runtime/knowledge/docs/ui/components/atoms/vc-container/vc-container.docs.md +21 -1
- package/runtime/knowledge/docs/ui/components/atoms/vc-link/vc-link.docs.md +22 -6
- package/runtime/knowledge/docs/ui/components/atoms/vc-skeleton/vc-skeleton.docs.md +2 -2
- package/runtime/knowledge/docs/ui/components/atoms/vc-status-icon/vc-status-icon.docs.md +32 -26
- package/runtime/knowledge/docs/ui/components/atoms/vc-tooltip/vc-tooltip.docs.md +3 -3
- package/runtime/knowledge/docs/ui/components/molecules/multilanguage-selector/multilanguage-selector.docs.md +1 -1
- package/runtime/knowledge/docs/ui/components/molecules/vc-accordion/vc-accordion.docs.md +1 -1
- package/runtime/knowledge/docs/ui/components/molecules/vc-breadcrumbs/vc-breadcrumbs.docs.md +1 -1
- package/runtime/knowledge/docs/ui/components/molecules/vc-dropdown-panel/vc-dropdown-panel.docs.md +13 -13
- package/runtime/knowledge/docs/ui/components/molecules/vc-form/vc-form.docs.md +1 -1
- package/runtime/knowledge/docs/ui/components/molecules/vc-pagination/vc-pagination.docs.md +1 -1
- package/runtime/knowledge/docs/ui/components/organisms/vc-app/vc-app.docs.md +4 -4
- package/runtime/knowledge/docs/ui/components/organisms/vc-blade/vc-blade.docs.md +39 -37
- package/runtime/knowledge/docs/ui/components/organisms/vc-data-table/vc-data-table.docs.md +17 -17
- package/runtime/knowledge/docs/ui/components/organisms/vc-dynamic-property/vc-dynamic-property.docs.md +16 -7
- package/runtime/knowledge/docs/ui/components/organisms/vc-sidebar/vc-sidebar.docs.md +1 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: useBroadcastFilter
|
|
3
|
+
category: composables
|
|
4
|
+
group: notifications
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# useBroadcastFilter
|
|
8
|
+
|
|
9
|
+
Controls which **broadcast** push notifications reach the store. The platform SignalR hub delivers messages through two channels: `Send` (targeted to a specific user) and `SendSystemEvents` (broadcast to everyone connected). Broadcasts run through the filter installed here; targeted messages are always accepted.
|
|
10
|
+
|
|
11
|
+
This is the standard mechanism for scoping a multi-tenant app — show a seller only the broadcasts that mention them, hide events from other tenants. Without a filter every broadcast lands in every user's history.
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
- Multi-tenant apps where the same broadcast topic carries events for different tenants (sellers, organizations, departments) and each user should only see their slice.
|
|
16
|
+
- Apps that want to drop noisy `IndexProgressPushNotification` or similar maintenance events for non-admin roles.
|
|
17
|
+
- When NOT to use: filtering targeted notifications. The platform already delivers `Send` messages only to the addressed user; `useBroadcastFilter` does not see them.
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { useBroadcastFilter, useUser } from "@vc-shell/framework";
|
|
23
|
+
import { onMounted } from "vue";
|
|
24
|
+
|
|
25
|
+
const { user } = useUser();
|
|
26
|
+
const { setBroadcastFilter } = useBroadcastFilter();
|
|
27
|
+
|
|
28
|
+
onMounted(() => {
|
|
29
|
+
setBroadcastFilter((msg) => msg.creator === user.value?.userName);
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Install the filter once at app bootstrap (or whenever the active user changes). Every incoming broadcast is run through it; messages that return `false` are dropped before they touch history, toasts, or subscribers.
|
|
34
|
+
|
|
35
|
+
## API
|
|
36
|
+
|
|
37
|
+
### Returns
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
interface UseBroadcastFilterReturn {
|
|
41
|
+
setBroadcastFilter(fn: (msg: PushNotification) => boolean): void;
|
|
42
|
+
clearBroadcastFilter(): void;
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Method | Type | Description |
|
|
47
|
+
| ---------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
48
|
+
| `setBroadcastFilter` | `((msg: PushNotification) => boolean) => void` | Install the filter. Replaces any previous filter — there is at most one active at a time. |
|
|
49
|
+
| `clearBroadcastFilter` | `() => void` | Remove the filter. All subsequent broadcasts are accepted. |
|
|
50
|
+
|
|
51
|
+
The filter returns `true` to **accept** a message, `false` to **drop** it.
|
|
52
|
+
|
|
53
|
+
## Common patterns
|
|
54
|
+
|
|
55
|
+
### Scope by current user
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
onMounted(() => {
|
|
59
|
+
setBroadcastFilter((msg) => msg.creator === user.value?.userName);
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`creator` is the user that originated the event on the platform side. This is the canonical "show me my own broadcasts" filter in multi-tenant back-office apps.
|
|
64
|
+
|
|
65
|
+
### Scope by tenant id
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
onMounted(() => {
|
|
69
|
+
setBroadcastFilter((msg) => (msg as TenantPush).sellerId === currentSellerId.value);
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
When your broadcast payloads carry a tenant id, gate on it instead of `creator`. The cast clarifies typing without expanding `PushNotification` for every caller.
|
|
74
|
+
|
|
75
|
+
### Re-install on user switch
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { watch } from "vue";
|
|
79
|
+
|
|
80
|
+
watch(
|
|
81
|
+
() => user.value?.userName,
|
|
82
|
+
(name) => {
|
|
83
|
+
if (!name) clearBroadcastFilter();
|
|
84
|
+
else setBroadcastFilter((msg) => msg.creator === name);
|
|
85
|
+
},
|
|
86
|
+
{ immediate: true },
|
|
87
|
+
);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
If the app supports user switching without a full reload (impersonation, multi-account), re-install the filter on every change. There is only one slot — installing again replaces the previous filter.
|
|
91
|
+
|
|
92
|
+
### Drop a noisy type entirely
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
setBroadcastFilter((msg) => msg.notifyType !== "IndexProgressPushNotification");
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Broadcast-only suppression. To suppress targeted messages too, set `toast: false` or `toast: { mode: "silent" }` on the type's `defineAppModule({ notifications })` config — that controls the toast surface; the history still records the event.
|
|
99
|
+
|
|
100
|
+
## Behavior
|
|
101
|
+
|
|
102
|
+
- The filter applies only to messages ingested with the `broadcast: true` flag (the SignalR `SendSystemEvents` channel).
|
|
103
|
+
- Targeted messages (`Send`) bypass the filter entirely.
|
|
104
|
+
- Installing a filter mid-session does not retroactively prune `history` or `realtime`. Past broadcasts stay; only future ones are filtered.
|
|
105
|
+
- The filter is a single function. To compose multiple predicates, `&&` them inside one callback.
|
|
106
|
+
|
|
107
|
+
## Tips
|
|
108
|
+
|
|
109
|
+
- **Install once, early.** Setting the filter in `App.vue` `onMounted` (after authentication) is the canonical placement, so messages arriving before the first blade mounts are already scoped.
|
|
110
|
+
- **Filter exceptions go straight to the console.** If your predicate throws, the message is dropped. Wrap the logic if you are reading off potentially missing fields.
|
|
111
|
+
- **Do not query the store from inside the filter.** The store is `useBroadcastFilter`'s parent — calling back into it during ingestion causes re-entrancy.
|
|
112
|
+
|
|
113
|
+
## Related
|
|
114
|
+
|
|
115
|
+
- [useNotificationStore](./useNotificationStore.md) — exposes the same set/clear methods plus the rest of the store API.
|
|
116
|
+
- [useBladeNotifications](./useBladeNotifications.md) — blade-scoped subscription that sees broadcasts after filtering.
|
|
117
|
+
- [Notifications concept page — broadcasts.](../../concepts/notifications.md#broadcast-vs-targeted)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: useNotificationContext
|
|
3
|
+
category: composables
|
|
4
|
+
group: notifications
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# useNotificationContext
|
|
8
|
+
|
|
9
|
+
Reads the current `PushNotification` payload inside a custom notification template. The framework provides the message via Vue's `inject()` from the dropdown or toast surface that hosts the template — the composable returns a reactive `ComputedRef` over it.
|
|
10
|
+
|
|
11
|
+
This is the one piece you write when a notification type registered with `defineAppModule({ notifications: { Type: { template } } })` needs a richer rendering than the default `NotificationTemplate` chrome — for example, formatting a domain-specific status, deriving a colored accent, or wiring a click handler that opens the relevant blade.
|
|
12
|
+
|
|
13
|
+
## When to use
|
|
14
|
+
|
|
15
|
+
- Implementing the `template` component for a notification type registered through `defineAppModule({ notifications })`.
|
|
16
|
+
- Reading typed payload fields (status, entity name, job id) to drive the template's layout, color, or actions.
|
|
17
|
+
- When NOT to use: anywhere outside a notification template. The composable throws if the inject context is not present.
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```vue
|
|
22
|
+
<script lang="ts" setup>
|
|
23
|
+
import { PushNotification, NotificationTemplate, useNotificationContext } from "@vc-shell/framework";
|
|
24
|
+
import { computed } from "vue";
|
|
25
|
+
|
|
26
|
+
interface IOrderPushNotification extends PushNotification {
|
|
27
|
+
orderId: string;
|
|
28
|
+
total: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const notificationRef = useNotificationContext<IOrderPushNotification>();
|
|
32
|
+
const notification = computed(() => notificationRef.value);
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<template>
|
|
36
|
+
<NotificationTemplate
|
|
37
|
+
:title="notification.title ?? ''"
|
|
38
|
+
:notification="notification"
|
|
39
|
+
>
|
|
40
|
+
<p>Order {{ notification.orderId }} — ${{ notification.total }}</p>
|
|
41
|
+
</NotificationTemplate>
|
|
42
|
+
</template>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## API
|
|
46
|
+
|
|
47
|
+
### Parameters
|
|
48
|
+
|
|
49
|
+
None. The composable is always called without arguments. Generic type parameter narrows the payload shape:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
function useNotificationContext<T extends PushNotification = PushNotification>(): ComputedRef<T>;
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Returns
|
|
56
|
+
|
|
57
|
+
`ComputedRef<T>` — reactive reference to the current `PushNotification` (or your extended subtype via the generic). Update reactively if the same template instance is reused for a refreshed payload (for example, when a progress message is updated through `notification.update`).
|
|
58
|
+
|
|
59
|
+
## Common patterns
|
|
60
|
+
|
|
61
|
+
### Compute display strings from payload fields
|
|
62
|
+
|
|
63
|
+
```vue
|
|
64
|
+
<script lang="ts" setup>
|
|
65
|
+
import { PushNotification, useNotificationContext, NotificationTemplate } from "@vc-shell/framework";
|
|
66
|
+
import { computed } from "vue";
|
|
67
|
+
import { useI18n } from "vue-i18n";
|
|
68
|
+
|
|
69
|
+
interface IProductPush extends PushNotification {
|
|
70
|
+
productName?: string;
|
|
71
|
+
newStatus?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const ctx = useNotificationContext<IProductPush>();
|
|
75
|
+
const notification = computed(() => ctx.value);
|
|
76
|
+
const { t } = useI18n({ useScope: "global" });
|
|
77
|
+
|
|
78
|
+
const title = computed(() => (notification.value.productName ? `${t("PRODUCTS.PUSH.PRODUCT")} "${notification.value.productName}" ${t("PRODUCTS.PUSH.UPDATE")}` : (notification.value.title ?? "")));
|
|
79
|
+
</script>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Open a blade on click
|
|
83
|
+
|
|
84
|
+
```vue
|
|
85
|
+
<script lang="ts" setup>
|
|
86
|
+
import { useBlade, useNotificationContext, NotificationTemplate } from "@vc-shell/framework";
|
|
87
|
+
import { computed } from "vue";
|
|
88
|
+
|
|
89
|
+
const { openBlade } = useBlade();
|
|
90
|
+
const ctx = useNotificationContext<IOrderPush>();
|
|
91
|
+
const notification = computed(() => ctx.value);
|
|
92
|
+
|
|
93
|
+
function onClick() {
|
|
94
|
+
if (!notification.value.orderId) return;
|
|
95
|
+
openBlade({ name: "OrderDetails", param: notification.value.orderId });
|
|
96
|
+
}
|
|
97
|
+
</script>
|
|
98
|
+
|
|
99
|
+
<template>
|
|
100
|
+
<NotificationTemplate
|
|
101
|
+
:title="notification.title ?? ''"
|
|
102
|
+
:notification="notification"
|
|
103
|
+
@click="onClick"
|
|
104
|
+
>
|
|
105
|
+
<p>{{ notification.description }}</p>
|
|
106
|
+
</NotificationTemplate>
|
|
107
|
+
</template>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`NotificationTemplate` re-emits the click event so the host (dropdown row, toast) can close itself before your handler runs.
|
|
111
|
+
|
|
112
|
+
### Color the template by status
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const notificationStyle = computed(() => {
|
|
116
|
+
switch (notification.value.newStatus) {
|
|
117
|
+
case "Approved":
|
|
118
|
+
return { color: "var(--success-400)", icon: "lucide-check-circle" };
|
|
119
|
+
case "RequestChanges":
|
|
120
|
+
return { color: "var(--danger-400)", icon: "lucide-alert-circle" };
|
|
121
|
+
case "WaitForApproval":
|
|
122
|
+
return { color: "var(--warning-600)", icon: "lucide-clock" };
|
|
123
|
+
default:
|
|
124
|
+
return { color: "var(--primary-400)", icon: "lucide-bell" };
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`NotificationTemplate` accepts `:color` and `:icon` props that line up with these computeds — the dropdown row and the toast use the same template, so the styling stays consistent across surfaces.
|
|
130
|
+
|
|
131
|
+
## Where the template runs
|
|
132
|
+
|
|
133
|
+
Notification templates render in two places:
|
|
134
|
+
|
|
135
|
+
- **In the bell dropdown**, as one row in the history list.
|
|
136
|
+
- **As a toast**, when the type's `toast.mode` is `"auto"` or `"progress"` (set the mode to `"silent"` to render only in the dropdown).
|
|
137
|
+
|
|
138
|
+
The template component must be **the same** in both — register it once on `defineAppModule({ notifications })` and the framework reuses it everywhere.
|
|
139
|
+
|
|
140
|
+
## Tips
|
|
141
|
+
|
|
142
|
+
- **Always type the generic.** `useNotificationContext<MyPushType>()` enables autocompletion on payload fields. Without it everything degrades to `PushNotification`.
|
|
143
|
+
- **`computed(() => ctx.value)` is idiomatic** in the example apps because consumers want a regular `Ref` shape to pass into child components and template bindings. Direct access via `ctx.value` is fine too.
|
|
144
|
+
- **Do not subscribe inside a template.** The template renders one message; if you need to react to other notifications, do that in a blade with `useBladeNotifications`.
|
|
145
|
+
|
|
146
|
+
## Related
|
|
147
|
+
|
|
148
|
+
- [useBladeNotifications](./useBladeNotifications.md) — subscribe to types inside a blade.
|
|
149
|
+
- [useNotificationStore](./useNotificationStore.md) — access the underlying store.
|
|
150
|
+
- [Notifications concept page.](../../concepts/notifications.md)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: useNotificationStore
|
|
3
|
+
category: composables
|
|
4
|
+
group: notifications
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# useNotificationStore
|
|
8
|
+
|
|
9
|
+
!!! warning "Advanced — most apps do not need this"
|
|
10
|
+
Reach for [useBladeNotifications](./useBladeNotifications.md), [useBroadcastFilter](./useBroadcastFilter.md), and the `notifications` option on `defineAppModule` first. The bell dropdown, unread badge, and toast pipeline are already wired by the shell. This composable is an **escape hatch** for the small set of cases where those facades do not fit.
|
|
11
|
+
|
|
12
|
+
Returns the singleton store that backs the framework's notification system. Direct access exposes the full reactive state plus low-level actions: subscribing, ingesting synthetic messages, controlling the broadcast filter, paging history.
|
|
13
|
+
|
|
14
|
+
## When to use
|
|
15
|
+
|
|
16
|
+
There are only two cases where this composable belongs in app code:
|
|
17
|
+
|
|
18
|
+
- **A test or scripted harness** that needs to push synthetic messages through the same pipeline SignalR uses, via `ingest`. Real-time production code never calls `ingest` directly.
|
|
19
|
+
- **A custom shell** that replaces the framework's bell dropdown entirely — typically a module-federation host with its own chrome — and therefore needs to bind to `history`, `unreadCount`, and `markAllAsRead` outside the default surface. Apps that use the standard `VcApp` shell do not need this; the dropdown is already there.
|
|
20
|
+
|
|
21
|
+
For everything else — reacting in a blade, gating broadcasts, registering types, displaying a toast — use the facades. Direct store mutations can bypass invariants the facades enforce (broadcast filter, scope-aware cleanup, type validation).
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { useNotificationStore } from "@vc-shell/framework";
|
|
27
|
+
|
|
28
|
+
const store = useNotificationStore();
|
|
29
|
+
|
|
30
|
+
await store.loadHistory(50);
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## API
|
|
34
|
+
|
|
35
|
+
The store exposes the full reactive state plus actions. The shape is summarized below; the underlying types live in `core/notifications/store.ts` and `core/notifications/types.ts`.
|
|
36
|
+
|
|
37
|
+
| Member | Type | Description |
|
|
38
|
+
| ------------------------ | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
39
|
+
| `registry` | `Map<string, NotificationTypeConfig>` | Notification types registered through `defineAppModule({ notifications })`. |
|
|
40
|
+
| `history` | `Ref<PushNotification[]>` | Full history (server-loaded plus ingested), newest first. |
|
|
41
|
+
| `realtime` | `Ref<PushNotification[]>` | Session-only realtime queue from the SignalR hub. Drives `messages` in `useBladeNotifications`. |
|
|
42
|
+
| `unreadCount` | `ComputedRef<number>` | Count of unread items in `history`. Drives the bell badge. |
|
|
43
|
+
| `hasUnread` | `ComputedRef<boolean>` | Convenience boolean over `unreadCount`. |
|
|
44
|
+
| `registerType(t, cfg)` | `(string, NotificationTypeConfig) => void` | Register a type. Called by the framework when `defineAppModule({ notifications })` runs — rarely needed manually. |
|
|
45
|
+
| `ingest(msg, opts?)` | `(PushNotification, { broadcast?: boolean }?) => void` | Push a message through the same pipeline SignalR uses. Broadcasts pass through the active broadcast filter. |
|
|
46
|
+
| `setBroadcastFilter(fn)` | `((PushNotification) => boolean) => void` | Install a filter for broadcast messages. Prefer [useBroadcastFilter](./useBroadcastFilter.md) — same method. |
|
|
47
|
+
| `clearBroadcastFilter()` | `() => void` | Remove the broadcast filter. |
|
|
48
|
+
| `markAsRead(msg)` | `(PushNotification) => void` | Mark one message as read. Mirrors to the server. |
|
|
49
|
+
| `markAllAsRead()` | `() => Promise<void>` | Optimistic mark-all with rollback on failure. |
|
|
50
|
+
| `loadHistory(take?)` | `(number?) => Promise<void>` | Fetch history from the platform. Default page size is 10. The shell already calls this at bootstrap. |
|
|
51
|
+
| `subscribe(opts)` | `({ types, filter?, handler? }) => () => void` | Low-level pub/sub. Returns an `unsub` function. Inside blades use [useBladeNotifications](./useBladeNotifications.md) — it wraps this and registers cleanup automatically. |
|
|
52
|
+
| `getByType(type)` | `(string) => PushNotification[]` | Filter `history` by `notifyType`. |
|
|
53
|
+
|
|
54
|
+
## Escape-hatch patterns
|
|
55
|
+
|
|
56
|
+
### Manually ingest a message (tests, scripted replay)
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const store = useNotificationStore();
|
|
60
|
+
|
|
61
|
+
store.ingest({
|
|
62
|
+
id: "test-1",
|
|
63
|
+
notifyType: "OrderCreatedDomainEvent",
|
|
64
|
+
title: "Test order",
|
|
65
|
+
isNew: true,
|
|
66
|
+
created: new Date().toISOString(),
|
|
67
|
+
} as PushNotification);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The ingest pipeline runs the configured toast logic and notifies subscribers exactly like a real SignalR message would, so you can verify the end-to-end behavior of `defineAppModule({ notifications })` + `useBladeNotifications` without a live hub.
|
|
71
|
+
|
|
72
|
+
`ingest` with `{ broadcast: true }` simulates `SendSystemEvents` and runs the broadcast filter; without it the message is treated as targeted and bypasses the filter.
|
|
73
|
+
|
|
74
|
+
### Custom shell surface (replace the bell dropdown)
|
|
75
|
+
|
|
76
|
+
When you are building a shell variant that omits the framework's bell dropdown — for example, a module-federation host that renders its own header — bind to the store directly:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { useNotificationStore } from "@vc-shell/framework";
|
|
80
|
+
import { onMounted } from "vue";
|
|
81
|
+
|
|
82
|
+
const store = useNotificationStore();
|
|
83
|
+
|
|
84
|
+
onMounted(() => store.loadHistory(100));
|
|
85
|
+
// Use store.history, store.unreadCount, store.markAllAsRead in your own components.
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
If you are using `VcApp` (the default shell), do **not** do this — the dropdown is already mounted and binding to the same store, so a second surface duplicates what is already on screen.
|
|
89
|
+
|
|
90
|
+
## Resolution
|
|
91
|
+
|
|
92
|
+
`useNotificationStore()` resolves in this order:
|
|
93
|
+
|
|
94
|
+
1. If called inside a Vue component's `setup` (or `app.runWithContext()`), it uses `inject(NotificationStoreKey)`.
|
|
95
|
+
2. Otherwise it returns a module-level singleton created on first call.
|
|
96
|
+
|
|
97
|
+
The fallback exists so module-federation remotes and standalone scripts see the same store the host app uses.
|
|
98
|
+
|
|
99
|
+
## Tips
|
|
100
|
+
|
|
101
|
+
- **Prefer facades.** Every time you find yourself writing `store.subscribe(...)` inside a blade, you want `useBladeNotifications`. Every time you write `store.setBroadcastFilter(...)`, you want `useBroadcastFilter`. The facades exist to keep cleanup, typing, and invariants in one place.
|
|
102
|
+
- **Do not iterate `realtime` for unread counts inside a blade.** That is what `useBladeNotifications` does correctly. Touching `realtime` directly couples your component to shell internals.
|
|
103
|
+
- **`loadHistory` replaces, then merges.** Calling it again pages in more entries and merges them with the existing history.
|
|
104
|
+
- **`markAllAsRead` is optimistic.** The local state flips immediately; if the server call fails the change is rolled back and an error toast surfaces.
|
|
105
|
+
- **Direct store mutations can bypass invariants.** The broadcast filter only runs through `ingest({ broadcast: true })`. Pushing arrays around `history.value` directly is not supported.
|
|
106
|
+
|
|
107
|
+
## Related
|
|
108
|
+
|
|
109
|
+
- [useBladeNotifications](./useBladeNotifications.md) — recommended scope-aware subscription. **Start here.**
|
|
110
|
+
- [useBroadcastFilter](./useBroadcastFilter.md) — broadcast acceptance gate.
|
|
111
|
+
- [useNotificationContext](./useNotificationContext.md) — payload access inside templates.
|
|
112
|
+
- [Notifications concept page.](../../concepts/notifications.md)
|
|
113
|
+
- [Notifications plugin reference.](../../plugins/notifications.md)
|
|
@@ -24,21 +24,18 @@ The notification system receives `PushNotification` messages from the platform S
|
|
|
24
24
|
|
|
25
25
|
## Architecture
|
|
26
26
|
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
+---> realtime[] (session-only, from SignalR)
|
|
40
|
-
+---> ToastController.handle() [Level 1: always-on]
|
|
41
|
-
+---> subscriber callbacks [Level 2: blade-scoped]
|
|
27
|
+
```mermaid
|
|
28
|
+
flowchart TD
|
|
29
|
+
H["PushNotification (SignalR hub)"] --> S["Send<br/>(targeted)"]
|
|
30
|
+
H --> B["SendSystemEvents<br/>(broadcast)"]
|
|
31
|
+
B --> F{"broadcastFilter?"}
|
|
32
|
+
F -->|accept| I["NotificationStore.ingest()"]
|
|
33
|
+
F -->|reject| X["dropped"]
|
|
34
|
+
S --> I
|
|
35
|
+
I --> Hist["history[]<br/>(persistent, loaded from API)"]
|
|
36
|
+
I --> RT["realtime[]<br/>(session-only)"]
|
|
37
|
+
I --> Toast["ToastController.handle()<br/>Level 1: always-on"]
|
|
38
|
+
I --> Sub["subscriber callbacks<br/>Level 2: blade-scoped"]
|
|
42
39
|
```
|
|
43
40
|
|
|
44
41
|
## API
|
|
@@ -684,4 +684,4 @@ These are used internally by `defineExtensionPoint` and `useExtensionPoint`. You
|
|
|
684
684
|
| Extension Point Store | `core/plugins/extension-points/store.ts` | Reactive registry implementation |
|
|
685
685
|
| ExtensionPoint Component | `core/plugins/extension-points/ExtensionPoint.vue` | Declarative render component |
|
|
686
686
|
| Types | `core/plugins/extension-points/types.ts` | `ExtensionComponent`, `ExtensionPointOptions` |
|
|
687
|
-
| Seller Details (usage example) |
|
|
687
|
+
| Seller Details (usage example) | Generated seller details module | Extension point host |
|