@vc-shell/vc-app-skill 2.0.0 → 2.0.2
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 +14 -0
- package/hooks/vc-app-check-update.js +14 -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/notifications/notifications.docs.md +38 -2
- package/runtime/knowledge/docs/injection-keys.docs.md +28 -22
- package/runtime/knowledge/docs/shell/dashboard/draggable-dashboard/dashboard-widget-skeleton.docs.md +26 -0
- package/runtime/knowledge/docs/shell/dashboard/draggable-dashboard/draggable-dashboard.docs.md +2 -0
- package/runtime/knowledge/migration-prompts/blade-form-migration.md +10 -7
- package/runtime/knowledge/migration-prompts/manual-migration-audit.md +40 -2
- package/runtime/knowledge/migration-prompts/notifications-migration.md +75 -27
- package/runtime/knowledge/migration-prompts/use-blade-migration.md +153 -0
- package/runtime/vc-app.md +13 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.0.2](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.1...v2.0.2) (2026-04-27)
|
|
4
|
+
|
|
5
|
+
**Note:** Version bump only for package @vc-shell/vc-app-skill
|
|
6
|
+
|
|
7
|
+
## [2.0.1](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.0...v2.0.1) (2026-04-24)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- enhance migrate transforms, fix notification race condition, update migration docs (#221) ([3c2134d](https://github.com/VirtoCommerce/vc-shell/commit/3c2134d0fa1016a2e4e075a12bb9df9a31b3d381)), closes [#221](https://github.com/VirtoCommerce/vc-shell/issues/221)
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
- **hooks:** update npm version retrieval in vc-app-check-update.js ([c8f0e68](https://github.com/VirtoCommerce/vc-shell/commit/c8f0e68ded994bdfb8d01dcf96e90184b9d278cd))
|
|
16
|
+
|
|
3
17
|
# [2.0.0](https://github.com/VirtoCommerce/vc-shell/compare/v1.2.3...v2.0.0) (2026-04-22)
|
|
4
18
|
|
|
5
19
|
### Features
|
|
@@ -35,7 +35,11 @@ if (!fs.existsSync(cacheDir)) {
|
|
|
35
35
|
fs.mkdirSync(cacheDir, { recursive: true });
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const child = spawn(
|
|
38
|
+
const child = spawn(
|
|
39
|
+
process.execPath,
|
|
40
|
+
[
|
|
41
|
+
"-e",
|
|
42
|
+
`
|
|
39
43
|
const fs = require("fs");
|
|
40
44
|
const { execSync } = require("child_process");
|
|
41
45
|
|
|
@@ -54,7 +58,7 @@ const child = spawn(process.execPath, ["-e", `
|
|
|
54
58
|
|
|
55
59
|
let latest = null;
|
|
56
60
|
try {
|
|
57
|
-
latest = execSync("npm view @vc-shell/vc-app-skill@
|
|
61
|
+
latest = execSync("npm view @vc-shell/vc-app-skill@latest version", { encoding: "utf8", timeout: 10000, windowsHide: true }).trim();
|
|
58
62
|
} catch (e) {}
|
|
59
63
|
|
|
60
64
|
const result = {
|
|
@@ -65,10 +69,13 @@ const child = spawn(process.execPath, ["-e", `
|
|
|
65
69
|
};
|
|
66
70
|
|
|
67
71
|
fs.writeFileSync(cacheFile, JSON.stringify(result));
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
`,
|
|
73
|
+
],
|
|
74
|
+
{
|
|
75
|
+
stdio: "ignore",
|
|
76
|
+
windowsHide: true,
|
|
77
|
+
detached: true,
|
|
78
|
+
},
|
|
79
|
+
);
|
|
73
80
|
|
|
74
81
|
child.unref();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vc-shell/vc-app-skill",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "AI coding skill for scaffolding and generating VirtoCommerce Shell applications. Works with Claude Code, OpenCode, Gemini, Codex, Cursor.",
|
|
5
5
|
"bin": "./bin/install.cjs",
|
|
6
6
|
"files": [
|
package/runtime/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.0.
|
|
1
|
+
2.0.2
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Synced from framework at commit
|
|
1
|
+
Synced from framework at commit a5e644392 on 2026-04-27T09:03:25.568Z
|
|
@@ -17,7 +17,12 @@ The notification system receives `PushNotification` messages from the platform S
|
|
|
17
17
|
```
|
|
18
18
|
PushNotification (SignalR)
|
|
19
19
|
|
|
|
20
|
-
|
|
20
|
+
┌─────┴─────┐
|
|
21
|
+
Send SendSystemEvents
|
|
22
|
+
| |
|
|
23
|
+
| broadcastFilter?
|
|
24
|
+
| |
|
|
25
|
+
v v
|
|
21
26
|
NotificationStore.ingest()
|
|
22
27
|
|
|
|
23
28
|
+---> history[] (persistent, loaded from API)
|
|
@@ -47,7 +52,9 @@ Returns the shared `NotificationStore` singleton. Resolution order:
|
|
|
47
52
|
| `unreadCount` | `ComputedRef<number>` | Count of unread notifications in history |
|
|
48
53
|
| `hasUnread` | `ComputedRef<boolean>` | Whether any unread notifications exist |
|
|
49
54
|
| `registerType(type, config)` | `(string, NotificationTypeConfig) => void` | Register a notification type with toast/template config |
|
|
50
|
-
| `ingest(message)`
|
|
55
|
+
| `ingest(message, opts?)` | `(PushNotification, {broadcast?}?) => void` | Process an incoming notification; broadcast messages are filtered |
|
|
56
|
+
| `setBroadcastFilter(fn)` | `((PushNotification) => boolean) => void` | Set filter for broadcast messages (SendSystemEvents) |
|
|
57
|
+
| `clearBroadcastFilter()` | `() => void` | Remove the broadcast filter |
|
|
51
58
|
| `markAsRead(message)` | `(PushNotification) => void` | Mark a single notification as read |
|
|
52
59
|
| `markAllAsRead()` | `() => Promise<void>` | Optimistic mark-all-as-read with server sync and rollback on failure |
|
|
53
60
|
| `loadHistory(take?)` | `(number?) => Promise<void>` | Load notification history from the API (default: 10) |
|
|
@@ -105,6 +112,35 @@ const notificationRef = useNotificationContext<IOrderNotification>();
|
|
|
105
112
|
// notificationRef.value.orderId, notificationRef.value.title, etc.
|
|
106
113
|
```
|
|
107
114
|
|
|
115
|
+
### `useBroadcastFilter()`
|
|
116
|
+
|
|
117
|
+
Controls which broadcast push notifications (`SendSystemEvents`) are accepted by the store. Targeted notifications (`Send`) are always accepted regardless of filter.
|
|
118
|
+
|
|
119
|
+
**File:** `composables/useBroadcastFilter.ts`
|
|
120
|
+
|
|
121
|
+
#### Returns
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
interface UseBroadcastFilterReturn {
|
|
125
|
+
setBroadcastFilter(fn: (msg: PushNotification) => boolean): void;
|
|
126
|
+
clearBroadcastFilter(): void;
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### Example
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { useBroadcastFilter } from "@vc-shell/framework";
|
|
134
|
+
|
|
135
|
+
const { setBroadcastFilter, clearBroadcastFilter } = useBroadcastFilter();
|
|
136
|
+
|
|
137
|
+
// Accept only notifications for the current seller
|
|
138
|
+
setBroadcastFilter((msg) => msg.creator === currentSellerId);
|
|
139
|
+
|
|
140
|
+
// Remove filter (accept all broadcast messages)
|
|
141
|
+
clearBroadcastFilter();
|
|
142
|
+
```
|
|
143
|
+
|
|
108
144
|
### Toast Controller
|
|
109
145
|
|
|
110
146
|
Handles toast popup display based on `NotificationTypeConfig.toast` settings. Created internally by the store.
|
|
@@ -19,22 +19,20 @@ This centralized approach has several advantages:
|
|
|
19
19
|
|
|
20
20
|
### Blade Navigation
|
|
21
21
|
|
|
22
|
-
| Key
|
|
23
|
-
|
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `InternalRoutesKey` | `BladeRoutesRecord[]` | Internal framework routes |
|
|
22
|
+
| Key | Type | Description |
|
|
23
|
+
| -------------------- | -------------------------------------- | -------------------------------------------- |
|
|
24
|
+
| `BladeDescriptorKey` | `ComputedRef<BladeDescriptor>` | Current blade descriptor metadata |
|
|
25
|
+
| `BladeBackButtonKey` | `Component \| undefined` | Custom back button component for a blade |
|
|
26
|
+
| `BladeDataKey` | _(from blade-navigation types)_ | Data passed between parent/child blades |
|
|
27
|
+
| `BladeContextKey` | `ComputedRef<Record<string, unknown>>` | Blade-exposed context for widgets/extensions |
|
|
28
|
+
| `BladeRoutesKey` | `BladeRoutesRecord[]` | Registered blade routes |
|
|
29
|
+
| `InternalRoutesKey` | `BladeRoutesRecord[]` | Internal framework routes |
|
|
31
30
|
|
|
32
31
|
### Notifications
|
|
33
32
|
|
|
34
|
-
| Key
|
|
35
|
-
|
|
|
36
|
-
| `
|
|
37
|
-
| `NotificationStoreKey` | `NotificationStore` | Shared notification store singleton |
|
|
33
|
+
| Key | Type | Description |
|
|
34
|
+
| ---------------------- | ------------------- | ----------------------------------- |
|
|
35
|
+
| `NotificationStoreKey` | `NotificationStore` | Shared notification store singleton |
|
|
38
36
|
|
|
39
37
|
### Services
|
|
40
38
|
|
|
@@ -42,7 +40,6 @@ This centralized approach has several advantages:
|
|
|
42
40
|
| ------------------------------- | ----------------------------- | ------------------------------ |
|
|
43
41
|
| `WidgetServiceKey` | `IWidgetService` | Widget registration and lookup |
|
|
44
42
|
| `DashboardServiceKey` | `IDashboardService` | Dashboard widget management |
|
|
45
|
-
| `GlobalSearchKey` | `GlobalSearchState` | Global search state |
|
|
46
43
|
| `MenuServiceKey` | `MenuService` | Main navigation menu |
|
|
47
44
|
| `SettingsMenuServiceKey` | `ISettingsMenuService` | Settings sidebar menu |
|
|
48
45
|
| `AppBarWidgetServiceKey` | `IAppBarWidgetService` | App bar widget slots |
|
|
@@ -81,14 +78,23 @@ This centralized approach has several advantages:
|
|
|
81
78
|
|
|
82
79
|
### Legacy Aliases (Deprecated)
|
|
83
80
|
|
|
84
|
-
| Deprecated
|
|
85
|
-
|
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
|
|
91
|
-
|
|
81
|
+
| Deprecated | Use Instead |
|
|
82
|
+
| ------------------- | -------------------- |
|
|
83
|
+
| `BladeDescriptor` | `BladeDescriptorKey` |
|
|
84
|
+
| `BLADE_BACK_BUTTON` | `BladeBackButtonKey` |
|
|
85
|
+
| `TOOLBAR_SERVICE` | `ToolbarServiceKey` |
|
|
86
|
+
| `EMBEDDED_MODE` | `EmbeddedModeKey` |
|
|
87
|
+
|
|
88
|
+
### Removed Keys (No Replacement)
|
|
89
|
+
|
|
90
|
+
These keys existed in v1.2.3 but have been removed from the framework entirely. There is no drop-in symbol replacement.
|
|
91
|
+
|
|
92
|
+
| Removed | Notes |
|
|
93
|
+
| ----------------------------- | ------------------------------------------------------------------------------ |
|
|
94
|
+
| `navigationViewLocation` | Internal framework concern — no public replacement |
|
|
95
|
+
| `BladeInstance` | Use `useBlade()` composable, or `inject(BladeDescriptorKey)` |
|
|
96
|
+
| `NotificationTemplatesSymbol` | Template system replaced by `NotificationStoreKey` + `useBladeNotifications()` |
|
|
97
|
+
| `GlobalSearchKey` | Internal concern now — no public replacement |
|
|
92
98
|
|
|
93
99
|
## Usage Examples
|
|
94
100
|
|
package/runtime/knowledge/docs/shell/dashboard/draggable-dashboard/dashboard-widget-skeleton.docs.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# DashboardWidgetSkeleton
|
|
2
|
+
|
|
3
|
+
Internal placeholder card used by `GridstackDashboard` while remote modules are still loading. Mimics the shape of `DashboardWidgetCard` (header with icon + title, stats row, content lines) with a shimmer animation. Not exported from `@vc-shell/framework` — consumed only inside the dashboard organism.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
- You won't use this directly. `GridstackDashboard` renders one per `SkeletonItem` while `ModulesReadyKey` resolves to `false`.
|
|
8
|
+
- If you build a custom dashboard layout and want the same loading aesthetic, copy this file rather than importing it — the component is intentionally internal so the dashboard team can change its markup at any time.
|
|
9
|
+
|
|
10
|
+
## Layout Contract
|
|
11
|
+
|
|
12
|
+
The skeleton has no props. Its parent positions it via inline `style` (`grid-column` / `grid-row`) inside a 12-column CSS grid. Card height/width is fully determined by the grid cell it occupies, so the skeleton stretches to fill its slot.
|
|
13
|
+
|
|
14
|
+
## Accessibility
|
|
15
|
+
|
|
16
|
+
- The wrapper has `aria-hidden="true"` so screen readers ignore the visual placeholders. The parent grid carries `role="status"` + `aria-busy="true"` to announce loading state once.
|
|
17
|
+
- Shimmer animation is disabled when the user prefers reduced motion.
|
|
18
|
+
|
|
19
|
+
## Design Tokens
|
|
20
|
+
|
|
21
|
+
Skeleton inherits dashboard card tokens where possible (`--dashboard-widget-card-background`, `--dashboard-widget-card-border-color`, `--dashboard-widget-card-border-radius`, `--dashboard-widget-card-shadow`) so its silhouette matches the real card. Shimmer colors are read from `--neutrals-100` / `--neutrals-200`.
|
|
22
|
+
|
|
23
|
+
## Related
|
|
24
|
+
|
|
25
|
+
- [DraggableDashboard](./draggable-dashboard.docs.md) — owns the skeleton grid and decides when to render placeholders.
|
|
26
|
+
- [DashboardWidgetCard](../dashboard-widget-card/dashboard-widget-card.docs.md) — the real card whose shape skeletons imitate.
|
package/runtime/knowledge/docs/shell/dashboard/draggable-dashboard/draggable-dashboard.docs.md
CHANGED
|
@@ -122,6 +122,8 @@ function resetLayout() {
|
|
|
122
122
|
- **Widget registration**: Widgets must be registered via `useDashboard().registerWidget()` before the dashboard mounts. The component reads the widget registry and creates grid items for each.
|
|
123
123
|
- **markRaw requirement**: Widget components must be wrapped in `markRaw()` when registering to prevent Vue from making them reactive (which would cause performance issues with the grid system).
|
|
124
124
|
- **Responsive behavior**: On mobile viewports, widgets stack vertically in a single column. Drag-and-drop is disabled on touch devices for better usability.
|
|
125
|
+
- **Module-ready gate**: When the host injects `ModulesReadyKey` (provided by `@vc-shell/mf-host`'s `registerRemoteModules`), the grid is held back until remote modules finish installing. This prevents widgets from mounting before their owning module's `defineAppModule.install` has merged its locales into vue-i18n — which would render translation keys instead of translated strings. Hosts without Module Federation (or tests/Storybook) inject nothing, and a fallback `ref(true)` keeps behavior unchanged.
|
|
126
|
+
- **Loading skeletons**: While `ModulesReadyKey` is `false`, the dashboard renders a CSS-grid of pulsing placeholder cards instead of an empty container. Card sizes are restored from the last persisted layout in `localStorage` (so returning users see placeholders matching their real widgets); first-time visitors get a default layout of four 6×6 placeholders. Animations respect `prefers-reduced-motion`.
|
|
125
127
|
|
|
126
128
|
## Tips
|
|
127
129
|
|
|
@@ -63,13 +63,14 @@ const { canSave, isModified, setBaseline, revert, setFieldError, errorBag } = us
|
|
|
63
63
|
closeConfirmMessage: () => t("MY_MODULE.ALERTS.CLOSE_CONFIRMATION"),
|
|
64
64
|
});
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
);
|
|
66
|
+
// Call setBaseline() AFTER data is loaded in onMounted.
|
|
67
|
+
// Do NOT use watch(item, ..., { once: true }) — it only detects ref
|
|
68
|
+
// replacement (item.value = newData), not in-place mutations
|
|
69
|
+
// (Object.assign(item.value, newData)).
|
|
70
|
+
onMounted(async () => {
|
|
71
|
+
await load(param.value);
|
|
72
|
+
setBaseline();
|
|
73
|
+
});
|
|
73
74
|
|
|
74
75
|
async function handleSave() {
|
|
75
76
|
await save(item.value);
|
|
@@ -77,6 +78,8 @@ async function handleSave() {
|
|
|
77
78
|
}
|
|
78
79
|
```
|
|
79
80
|
|
|
81
|
+
**IMPORTANT — `setBaseline()` placement:** Always call `setBaseline()` AFTER the async load completes, inside `onMounted` or after the load function returns. A `watch(data, ..., { once: true })` watcher fails when the composable mutates data in-place via `Object.assign(data.value, loadedData)` instead of replacing the ref — the watcher never fires because the `.value` reference doesn't change.
|
|
82
|
+
|
|
80
83
|
## RULE 2: Remove onBeforeClose Entirely
|
|
81
84
|
|
|
82
85
|
`useBladeForm` handles the unsaved-changes guard automatically via `autoBeforeClose` (defaults to `true`).
|
|
@@ -90,7 +90,45 @@ await closeSelf();
|
|
|
90
90
|
|
|
91
91
|
If `closeBlade(index)` was used to close by index, refactor to close current blade only (or use explicit navigation logic) — do not keep index-based close calls.
|
|
92
92
|
|
|
93
|
-
## RULE 5:
|
|
93
|
+
## RULE 5: Remove `resolveBladeByName` — pass name directly to `openBlade`
|
|
94
|
+
|
|
95
|
+
**BEFORE:**
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const { openBlade, resolveBladeByName } = useBlade();
|
|
99
|
+
openBlade({ blade: resolveBladeByName("OrderDetails")!, param: orderId });
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**AFTER:**
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const { openBlade } = useBlade();
|
|
106
|
+
openBlade({ name: "OrderDetails", param: orderId });
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Remove `resolveBladeByName` from the destructuring. The `blade` property in the openBlade argument is replaced by `name` (string).
|
|
110
|
+
|
|
111
|
+
## RULE 6: Replace `onParentCall` with `callParent`
|
|
112
|
+
|
|
113
|
+
**BEFORE:**
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const { onParentCall } = useBladeNavigation();
|
|
117
|
+
onParentCall({ method: "reload" });
|
|
118
|
+
onParentCall({ method: "updateItem", args: newItem });
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**AFTER:**
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
const { callParent } = useBlade();
|
|
125
|
+
await callParent("reload");
|
|
126
|
+
await callParent("updateItem", newItem);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Note: `callParent` is async and returns a Promise. Add `await` if the result matters.
|
|
130
|
+
|
|
131
|
+
## RULE 7: Consolidate multiple `useBlade()` calls per file
|
|
94
132
|
|
|
95
133
|
Use one `useBlade()` destructuring per component/composable unless there is a strong technical reason.
|
|
96
134
|
|
|
@@ -111,7 +149,7 @@ const { openBlade, closeSelf, callParent, param } = useBlade();
|
|
|
111
149
|
|
|
112
150
|
After migration:
|
|
113
151
|
|
|
114
|
-
1. Search and remove stale APIs: `useExternalWidgets`, `import moment`, `useFunctions(`, `closeBlade(`.
|
|
152
|
+
1. Search and remove stale APIs: `useExternalWidgets`, `import moment`, `useFunctions(`, `closeBlade(`, `resolveBladeByName`, `onParentCall`.
|
|
115
153
|
2. Search for duplicate `useBlade()` calls in the same file and consolidate.
|
|
116
154
|
3. Run `npx vue-tsc --noEmit`.
|
|
117
155
|
4. Run `yarn build`.
|
|
@@ -152,17 +152,28 @@ Toast options:
|
|
|
152
152
|
|
|
153
153
|
## RULE 4: Replace useNotifications in Blades
|
|
154
154
|
|
|
155
|
-
Replace `useNotifications()` with `useBladeNotifications()`. Remove `setNotificationHandler`, `moduleNotifications`
|
|
155
|
+
Replace `useNotifications()` with `useBladeNotifications()`. Remove `setNotificationHandler`, the entire `watch(moduleNotifications, ...)` block, manual toast management, and `notifyType` from `defineOptions`.
|
|
156
|
+
|
|
157
|
+
### What to DELETE (toast management is now automatic via module config):
|
|
158
|
+
|
|
159
|
+
- The entire `watch(moduleNotifications/messages, ..., { deep: true })` block
|
|
160
|
+
- All `notification()` / `notification.update()` calls related to notification messages
|
|
161
|
+
- The `notificationId` ref used for toast tracking
|
|
162
|
+
- `setNotificationHandler(...)` calls
|
|
163
|
+
- `markAsRead()` calls inside toast `onClose` handlers
|
|
164
|
+
- `notifyType` from `defineOptions`/`defineBlade`
|
|
165
|
+
|
|
166
|
+
### What to EXTRACT into `onMessage`:
|
|
167
|
+
|
|
168
|
+
- Only **data-reload logic** from the watch callback (e.g., `getTasks()`, `refreshData()`, `reload()`)
|
|
169
|
+
- Add `filter` if the callback had blade-specific conditions (e.g., `message.profileId === param.value`)
|
|
170
|
+
|
|
171
|
+
### Simple case: `setNotificationHandler`
|
|
156
172
|
|
|
157
173
|
**BEFORE:**
|
|
158
174
|
|
|
159
175
|
```typescript
|
|
160
|
-
|
|
161
|
-
name: "MyBlade",
|
|
162
|
-
notifyType: "MyDomainEvent",
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
const { markAsRead, setNotificationHandler, moduleNotifications } = useNotifications("MyDomainEvent");
|
|
176
|
+
const { markAsRead, setNotificationHandler } = useNotifications("MyDomainEvent");
|
|
166
177
|
|
|
167
178
|
setNotificationHandler((message) => {
|
|
168
179
|
if (message.title) {
|
|
@@ -173,12 +184,54 @@ setNotificationHandler((message) => {
|
|
|
173
184
|
});
|
|
174
185
|
}
|
|
175
186
|
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**AFTER:**
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
useBladeNotifications({
|
|
193
|
+
types: ["MyDomainEvent"],
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Complex case: `watch` with toast management + data reload
|
|
198
|
+
|
|
199
|
+
**BEFORE:**
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
const { moduleNotifications, markAsRead } = useNotifications("ImportPushNotification");
|
|
203
|
+
const notificationId = ref();
|
|
176
204
|
|
|
177
|
-
// or watch pattern:
|
|
178
205
|
watch(
|
|
179
206
|
moduleNotifications,
|
|
180
207
|
(newVal) => {
|
|
181
|
-
|
|
208
|
+
(newVal as ImportPushNotification[]).forEach((message) => {
|
|
209
|
+
const messageContent = message.profileName ? `${message.profileName}: ${message.title}` : message.title;
|
|
210
|
+
|
|
211
|
+
// DATA RELOAD — extract this into onMessage
|
|
212
|
+
if (!importStarted.value && message.profileId === param.value) {
|
|
213
|
+
getTasks({ profileId: message.profileId, importJobId: message.jobId });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// TOAST MANAGEMENT — delete all of this (handled by module config)
|
|
217
|
+
if (!message.finished) {
|
|
218
|
+
if (!notificationId.value && messageContent) {
|
|
219
|
+
notificationId.value = notification(messageContent, { timeout: false });
|
|
220
|
+
} else {
|
|
221
|
+
notification.update(notificationId.value, { content: messageContent });
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
notification.update(notificationId.value, {
|
|
225
|
+
timeout: 5000,
|
|
226
|
+
content: messageContent,
|
|
227
|
+
type: message.title === "Import failed" ? "error" : "success",
|
|
228
|
+
onClose() {
|
|
229
|
+
markAsRead(message);
|
|
230
|
+
notificationId.value = undefined;
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
});
|
|
182
235
|
},
|
|
183
236
|
{ deep: true },
|
|
184
237
|
);
|
|
@@ -187,28 +240,23 @@ watch(
|
|
|
187
240
|
**AFTER:**
|
|
188
241
|
|
|
189
242
|
```typescript
|
|
190
|
-
defineOptions({
|
|
191
|
-
name: "MyBlade",
|
|
192
|
-
// notifyType removed
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
const { messages } = useBladeNotifications({
|
|
196
|
-
types: ["MyDomainEvent"],
|
|
197
|
-
onMessage: () => reloadData(),
|
|
198
|
-
});
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
For progress notifications:
|
|
202
|
-
|
|
203
|
-
```typescript
|
|
204
|
-
// Module config: notifications: { MyProgressEvent: { toast: { mode: "progress" } } }
|
|
205
243
|
useBladeNotifications({
|
|
206
|
-
types: ["
|
|
207
|
-
filter: (msg) => msg.
|
|
208
|
-
onMessage: (msg) =>
|
|
244
|
+
types: ["ImportPushNotification"],
|
|
245
|
+
filter: (msg) => (msg as ImportPushNotification).profileId === param.value,
|
|
246
|
+
onMessage: (msg) => {
|
|
247
|
+
if (!importStarted.value) {
|
|
248
|
+
getTasks({
|
|
249
|
+
profileId: (msg as ImportPushNotification).profileId,
|
|
250
|
+
importJobId: (msg as ImportPushNotification).jobId,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
},
|
|
209
254
|
});
|
|
255
|
+
// DELETE: notificationId ref, watch block, notification()/notification.update() calls
|
|
210
256
|
```
|
|
211
257
|
|
|
258
|
+
Toast appearance is configured in `defineAppModule({ notifications: { ImportPushNotification: { toast: { mode: "progress" } } } })`. The `markAsRead` is handled automatically.
|
|
259
|
+
|
|
212
260
|
## Verification
|
|
213
261
|
|
|
214
262
|
After migration:
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: use-blade-migration
|
|
3
|
+
description: AI transformation rules for useBladeNavigation → useBlade, resolveBladeByName removal, onParentCall → callParent.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Blade Navigation Migration: useBladeNavigation → useBlade
|
|
7
|
+
|
|
8
|
+
Replace `useBladeNavigation()`, `useBladeContext()`, `resolveBladeByName()`, and `onParentCall()` with the unified `useBlade()` composable.
|
|
9
|
+
|
|
10
|
+
## RULE 1: Replace useBladeNavigation / useBladeContext with useBlade
|
|
11
|
+
|
|
12
|
+
Rename the import and call site. Merge destructured properties from both into a single `useBlade()` call.
|
|
13
|
+
|
|
14
|
+
**BEFORE:**
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { useBladeNavigation } from "@vc-shell/framework";
|
|
18
|
+
const { openBlade, resolveBladeByName, onBeforeClose } = useBladeNavigation();
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**AFTER:**
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { useBlade } from "@vc-shell/framework";
|
|
25
|
+
const { openBlade, onBeforeClose } = useBlade();
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
If both `useBladeNavigation()` and `useBladeContext()` are used, merge into one `useBlade()`:
|
|
29
|
+
|
|
30
|
+
**BEFORE:**
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
const blade = useBlade();
|
|
34
|
+
const { openBlade, onBeforeClose } = useBladeNavigation();
|
|
35
|
+
registerWidget(myWidget, blade?.value.id ?? "");
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**AFTER:**
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
const { id: bladeId, openBlade, onBeforeClose } = useBlade();
|
|
42
|
+
registerWidget(myWidget, bladeId.value);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## RULE 2: Remove resolveBladeByName
|
|
46
|
+
|
|
47
|
+
`resolveBladeByName` is no longer needed — pass the blade name directly to `openBlade`.
|
|
48
|
+
|
|
49
|
+
**BEFORE:**
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
openBlade({ blade: resolveBladeByName("OrderDetails")!, param: orderId });
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**AFTER:**
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
openBlade({ name: "OrderDetails", param: orderId });
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Also handle the pattern with options:
|
|
62
|
+
|
|
63
|
+
**BEFORE:**
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
openBlade({
|
|
67
|
+
blade: resolveBladeByName("ImportProcess")!,
|
|
68
|
+
param: profileId,
|
|
69
|
+
options: { title: profile.name, importJobId: jobId },
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**AFTER:**
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
openBlade({
|
|
77
|
+
name: "ImportProcess",
|
|
78
|
+
param: profileId,
|
|
79
|
+
options: { title: profile.name, importJobId: jobId },
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Remove the `resolveBladeByName` destructure from `useBlade()` since it's no longer used.
|
|
84
|
+
|
|
85
|
+
## RULE 3: Replace onParentCall with callParent
|
|
86
|
+
|
|
87
|
+
**BEFORE:**
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
const { onParentCall } = useBladeNavigation();
|
|
91
|
+
onParentCall({ method: "reload" });
|
|
92
|
+
onParentCall({ method: "updateItem", args: newItem });
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**AFTER:**
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const { callParent } = useBlade();
|
|
99
|
+
await callParent("reload");
|
|
100
|
+
await callParent("updateItem", newItem);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Note: `callParent` is async and returns a Promise.
|
|
104
|
+
|
|
105
|
+
## RULE 4: Invert onBeforeClose Boolean
|
|
106
|
+
|
|
107
|
+
The guard semantics are **inverted** in the new API:
|
|
108
|
+
|
|
109
|
+
| Legacy | New |
|
|
110
|
+
| ------------------------------ | ----------------------------- |
|
|
111
|
+
| `return false` → prevent close | `return true` → prevent close |
|
|
112
|
+
| `return true` → allow close | `return false` → allow close |
|
|
113
|
+
|
|
114
|
+
**BEFORE:**
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
onBeforeClose(async () => {
|
|
118
|
+
if (isModified.value) {
|
|
119
|
+
return await showConfirmation(msg); // true = allow, false = prevent
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**AFTER:**
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
onBeforeClose(async () => {
|
|
128
|
+
if (isModified.value) {
|
|
129
|
+
return !(await showConfirmation(msg)); // true = prevent, false = allow
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
If `useBladeForm()` is used, `onBeforeClose` can be removed entirely — `useBladeForm` handles it automatically.
|
|
135
|
+
|
|
136
|
+
## RULE 5: Clean Up Unused Imports
|
|
137
|
+
|
|
138
|
+
After applying all rules, remove:
|
|
139
|
+
|
|
140
|
+
- `useBladeNavigation` import
|
|
141
|
+
- `useBladeContext` import
|
|
142
|
+
- `resolveBladeByName` import (if it was imported separately)
|
|
143
|
+
- Any unused destructured variables
|
|
144
|
+
|
|
145
|
+
## Verification
|
|
146
|
+
|
|
147
|
+
After migration:
|
|
148
|
+
|
|
149
|
+
1. Run `npx vue-tsc --noEmit` to verify no TypeScript errors
|
|
150
|
+
2. Confirm `openBlade` calls navigate correctly
|
|
151
|
+
3. Confirm blade close confirmation dialogs still appear when data is modified
|
|
152
|
+
4. Confirm no remaining imports of `useBladeNavigation`, `useBladeContext`, or `resolveBladeByName`
|
|
153
|
+
5. Confirm no remaining `onParentCall` calls
|
package/runtime/vc-app.md
CHANGED
|
@@ -1600,19 +1600,19 @@ Parse the "Manual Migration Required" section. Extract each topic heading and th
|
|
|
1600
1600
|
|
|
1601
1601
|
Map topic headings to migration prompt files and pattern files:
|
|
1602
1602
|
|
|
1603
|
-
| Report Heading contains (or equals transform name)
|
|
1604
|
-
|
|
|
1605
|
-
| Widget / widgets-migration
|
|
1606
|
-
| Form Management / useBladeForm / use-blade-form
|
|
1607
|
-
| Injection Key / remove-deprecated-aliases
|
|
1608
|
-
| NSwag / DTO / Clone-then-mutate / nswag-class-to-interface
|
|
1609
|
-
| Reusable Blade Components / blade-props-simplification
|
|
1610
|
-
| Notification / notification-migration
|
|
1611
|
-
| VcTable / DataTable / vctable-audit
|
|
1612
|
-
| Icon / material- / bi- / fa- / icon-audit
|
|
1613
|
-
| Assets API / useAssets / useAssetsManager / use-assets-migration
|
|
1614
|
-
| Manual Migration Audit / useExternalWidgets / moment / useFunctions / manual-migration-audit | `manual-migration-audit` | `{KNOWLEDGE_BASE}/migration-prompts/manual-migration-audit.md` | — |
|
|
1615
|
-
| Pagination / useDataTablePagination / use-data-table-pagination-audit
|
|
1603
|
+
| Report Heading contains (or equals transform name) | Canonical topic name | Migration Prompt | Pattern |
|
|
1604
|
+
| -------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------- |
|
|
1605
|
+
| Widget / widgets-migration | `widgets-migration` | `{KNOWLEDGE_BASE}/migration-prompts/widgets-migration.md` | `{KNOWLEDGE_BASE}/patterns/blade-widget.md` |
|
|
1606
|
+
| Form Management / useBladeForm / use-blade-form | `use-blade-form` | `{KNOWLEDGE_BASE}/migration-prompts/blade-form-migration.md` | `{KNOWLEDGE_BASE}/patterns/form-validation.md` |
|
|
1607
|
+
| Injection Key / remove-deprecated-aliases | `remove-deprecated-aliases` | `{KNOWLEDGE_BASE}/migration-prompts/blade-props-migration.md` | `{KNOWLEDGE_BASE}/patterns/blade-navigation.md` |
|
|
1608
|
+
| NSwag / DTO / Clone-then-mutate / nswag-class-to-interface | `nswag-class-to-interface` | `{KNOWLEDGE_BASE}/migration-prompts/nswag-migration.md` | — |
|
|
1609
|
+
| Reusable Blade Components / blade-props-simplification | `blade-props-simplification` | `{KNOWLEDGE_BASE}/migration-prompts/blade-props-migration.md` | `{KNOWLEDGE_BASE}/patterns/child-blade-flow.md` |
|
|
1610
|
+
| Notification / notification-migration | `notification-migration` | `{KNOWLEDGE_BASE}/migration-prompts/notifications-migration.md` | `{KNOWLEDGE_BASE}/patterns/signalr-notifications.md` |
|
|
1611
|
+
| VcTable / DataTable / vctable-audit | `vctable-audit` | `{KNOWLEDGE_BASE}/migration-prompts/datatable-migration.md` | `{KNOWLEDGE_BASE}/patterns/datatable-pattern.md` |
|
|
1612
|
+
| Icon / material- / bi- / fa- / icon-audit | `icon-audit` | `{KNOWLEDGE_BASE}/migration-prompts/icon-migration.md` | — |
|
|
1613
|
+
| Assets API / useAssets / useAssetsManager / use-assets-migration | `use-assets-migration` | `{KNOWLEDGE_BASE}/migration-prompts/use-assets-migration.md` | `{KNOWLEDGE_BASE}/patterns/assets-management.md` |
|
|
1614
|
+
| Manual Migration Audit / useExternalWidgets / moment / useFunctions / resolveBladeByName / onParentCall / manual-migration-audit | `manual-migration-audit` | `{KNOWLEDGE_BASE}/migration-prompts/manual-migration-audit.md` | — |
|
|
1615
|
+
| Pagination / useDataTablePagination / use-data-table-pagination-audit | `use-data-table-pagination-audit` | `{KNOWLEDGE_BASE}/migration-prompts/use-data-table-pagination-migration.md` | — |
|
|
1616
1616
|
|
|
1617
1617
|
Build the `topics` array for the migration-agent using the canonical topic names above.
|
|
1618
1618
|
|