@vc-shell/vc-app-skill 2.0.0 → 2.0.1
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 +10 -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/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,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.0.1](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.0...v2.0.1) (2026-04-24)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- 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)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **hooks:** update npm version retrieval in vc-app-check-update.js ([c8f0e68](https://github.com/VirtoCommerce/vc-shell/commit/c8f0e68ded994bdfb8d01dcf96e90184b9d278cd))
|
|
12
|
+
|
|
3
13
|
# [2.0.0](https://github.com/VirtoCommerce/vc-shell/compare/v1.2.3...v2.0.0) (2026-04-22)
|
|
4
14
|
|
|
5
15
|
### 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.1",
|
|
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.1
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Synced from framework at commit
|
|
1
|
+
Synced from framework at commit 5f8005c08 on 2026-04-24T08:05:31.316Z
|
|
@@ -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.
|
|
@@ -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
|
|