@vc-shell/vc-app-skill 2.0.0-alpha.31 → 2.0.0-alpha.33
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 +19 -0
- package/package.json +1 -1
- package/runtime/VERSION +1 -1
- package/runtime/agents/migration-agent.md +83 -0
- package/runtime/knowledge/docs/_BUILD_HASH.md +1 -1
- package/runtime/knowledge/docs/core/composables/bladeContext/index.docs.md +111 -0
- package/runtime/knowledge/docs/core/composables/useBladeForm/useBladeForm.docs.md +167 -0
- package/runtime/knowledge/docs/core/composables/useBladeWidgets/index.docs.md +305 -0
- package/runtime/knowledge/docs/core/composables/useMenuExpanded/index.docs.md +83 -0
- package/runtime/knowledge/docs/core/utilities/thumbnail/thumbnail.docs.md +116 -0
- package/runtime/knowledge/docs/shell/components/change-password-button/change-password-button.docs.md +1 -1
- package/runtime/knowledge/docs/ui/components/atoms/vc-card/vc-card.docs.md +4 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-accordion/vc-accordion.docs.md +4 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-checkbox/vc-checkbox.docs.md +5 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-checkbox-group/vc-checkbox-group.docs.md +5 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-color-input/vc-color-input.docs.md +5 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-date-picker/vc-date-picker.docs.md +7 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-editor/vc-editor.docs.md +5 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-field/vc-field.docs.md +5 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-file-upload/vc-file-upload.docs.md +5 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-input/vc-input.docs.md +7 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-input-currency/vc-input-currency.docs.md +7 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-input-dropdown/vc-input-dropdown.docs.md +7 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-multivalue/vc-multivalue.docs.md +7 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-radio-button/vc-radio-button.docs.md +5 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-radio-group/vc-radio-group.docs.md +5 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-rating/vc-rating.docs.md +5 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-select/vc-select.docs.md +7 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-slider/vc-slider.docs.md +5 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-switch/vc-switch.docs.md +5 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-textarea/vc-textarea.docs.md +7 -0
- package/runtime/knowledge/docs/ui/components/organisms/vc-blade/vc-blade.docs.md +30 -0
- package/runtime/knowledge/docs/ui/components/organisms/vc-data-table/vc-data-table.docs.md +28 -0
- package/runtime/knowledge/migration-prompts/blade-form-migration.md +246 -0
- package/runtime/knowledge/migration-prompts/blade-props-migration.md +195 -0
- package/runtime/knowledge/migration-prompts/notifications-migration.md +218 -0
- package/runtime/knowledge/migration-prompts/nswag-migration.md +248 -0
- package/runtime/knowledge/migration-prompts/widgets-migration.md +157 -0
- package/runtime/vc-app.md +126 -0
- package/runtime/knowledge/docs/core/constants/constants.docs.md +0 -185
- /package/runtime/knowledge/docs/shell/{pages → auth}/ChangePasswordPage/change-password-page.docs.md +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
# [2.0.0-alpha.33](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.0-alpha.32...v2.0.0-alpha.33) (2026-04-14)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **vc-app-skill:** add VC_SHELL_MIGRATE_CLI env override for local testing ([010d2f2](https://github.com/VirtoCommerce/vc-shell/commit/010d2f2ecb29d31d9134406bcd18e46e5883208b))
|
|
7
|
+
* **vc-app-skill:** resolve migrate CLI from project node_modules before npx fallback ([82a8156](https://github.com/VirtoCommerce/vc-shell/commit/82a81563f7fdb94a94a7b8a80796ecffc671a39c))
|
|
8
|
+
* **vc-app-skill:** resolve migrate CLI locally in dev mode, fallback to npx ([89888a0](https://github.com/VirtoCommerce/vc-shell/commit/89888a06cc0b4a1519f46f070ee06ebb0d82fd09))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **vc-app-skill:** add /vc-app migrate command with full migration pipeline ([35a8b11](https://github.com/VirtoCommerce/vc-shell/commit/35a8b119457b14f6f4626caf133476222afd6663))
|
|
14
|
+
* **vc-app-skill:** add migration prompt knowledge base (nswag, widgets, form, blade-props, notifications) ([a0b0eb2](https://github.com/VirtoCommerce/vc-shell/commit/a0b0eb2a96521916a72bdbf787961c1c96f96f6d))
|
|
15
|
+
* **vc-app-skill:** add migration-agent subagent prompt ([29ac155](https://github.com/VirtoCommerce/vc-shell/commit/29ac1550c8e3c9b71d5dbc2ade7ea86b4d40213f))
|
|
16
|
+
# [2.0.0-alpha.32](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.0-alpha.31...v2.0.0-alpha.32) (2026-04-02)
|
|
17
|
+
|
|
18
|
+
**Note:** Version bump only for package @vc-shell/vc-app-skill
|
|
19
|
+
|
|
1
20
|
# [2.0.0-alpha.31](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.0-alpha.30...v2.0.0-alpha.31) (2026-04-01)
|
|
2
21
|
|
|
3
22
|
**Note:** Version bump only for package @vc-shell/vc-app-skill
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vc-shell/vc-app-skill",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.33",
|
|
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.0-alpha.
|
|
1
|
+
2.0.0-alpha.33
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: migration-agent
|
|
3
|
+
description: Performs AI-powered manual migrations on files identified by MIGRATION_REPORT.md, using transformation rules from migration prompts.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## Input Contract
|
|
7
|
+
|
|
8
|
+
```json
|
|
9
|
+
{
|
|
10
|
+
"required": {
|
|
11
|
+
"cwd": "string — absolute path to project root",
|
|
12
|
+
"reportPath": "string — path to MIGRATION_REPORT.md",
|
|
13
|
+
"topics": "array — migration topics to process, each with: { name, affectedFiles, migrationPromptPath, patternPath? }"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Knowledge Loading
|
|
19
|
+
|
|
20
|
+
For each topic in `topics`:
|
|
21
|
+
1. Read the migration prompt from `migrationPromptPath` — these contain specific transformation rules with BEFORE/AFTER examples
|
|
22
|
+
2. If `patternPath` is provided, read the pattern file — this shows what correct target code looks like
|
|
23
|
+
|
|
24
|
+
Do NOT read all prompts upfront. Load each topic's knowledge just before processing that topic.
|
|
25
|
+
|
|
26
|
+
## Processing Strategy
|
|
27
|
+
|
|
28
|
+
### Topic Order
|
|
29
|
+
|
|
30
|
+
Process topics in this order (dependencies first):
|
|
31
|
+
1. `notifications-migration` — may create new files and restructure directories
|
|
32
|
+
2. `nswag-migration` — type-level changes that affect all other code
|
|
33
|
+
3. `widgets-migration` — creates new composable files referenced by blades
|
|
34
|
+
4. `blade-form-migration` — depends on correct types from nswag
|
|
35
|
+
5. `blade-props-migration` — final cleanup of reusable components
|
|
36
|
+
|
|
37
|
+
Skip topics not present in the `topics` input.
|
|
38
|
+
|
|
39
|
+
### Per-File Strategy
|
|
40
|
+
|
|
41
|
+
For each affected file in the current topic:
|
|
42
|
+
|
|
43
|
+
1. **Read the file** completely
|
|
44
|
+
2. **Apply transformation rules** from the loaded migration prompt — follow the BEFORE/AFTER patterns exactly
|
|
45
|
+
3. **Create new files if required** by the prompt (e.g. `widgets/useXxxWidgets.ts` for widget migration)
|
|
46
|
+
4. **Write the modified file**
|
|
47
|
+
5. **Type-check:** Run `cd {cwd} && npx vue-tsc --noEmit 2>&1 | head -30` to check for errors
|
|
48
|
+
6. **If errors in this file:** Read error messages, fix, re-check. Max 3 attempts.
|
|
49
|
+
7. **If fixed or clean:** Commit: `fix(migrate): {topic name} — {filename}`
|
|
50
|
+
8. **If still broken after 3 attempts:** Revert the file (`git checkout -- {filepath}`), report as "needs manual intervention", continue to next file
|
|
51
|
+
|
|
52
|
+
### Important Rules
|
|
53
|
+
|
|
54
|
+
1. **Only modify files listed in the topic's `affectedFiles`** — do not touch other files unless creating new files required by the migration prompt (e.g. widget composables)
|
|
55
|
+
2. **Follow transformation rules exactly** — each migration prompt has specific BEFORE/AFTER patterns. Apply them mechanically.
|
|
56
|
+
3. **Do not add features or refactor** — only transform existing code to the new API
|
|
57
|
+
4. **Preserve all business logic** — the behavior must stay identical after migration
|
|
58
|
+
5. **Commit after each successfully migrated file** — atomic commits for easy rollback
|
|
59
|
+
6. **Do not modify `api_client/` directory** — generated code, not consumer code
|
|
60
|
+
7. **Do not modify `node_modules/`**
|
|
61
|
+
|
|
62
|
+
## Output
|
|
63
|
+
|
|
64
|
+
When all topics are processed, report:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
## Migration Agent Report
|
|
68
|
+
|
|
69
|
+
### Completed
|
|
70
|
+
- {topic}: {N} files migrated
|
|
71
|
+
- src/path/to/file.vue ✅
|
|
72
|
+
- src/path/to/other.ts ✅
|
|
73
|
+
|
|
74
|
+
### Failed (needs manual intervention)
|
|
75
|
+
- {topic}: {M} files failed
|
|
76
|
+
- src/path/to/problem.vue — {error description}
|
|
77
|
+
|
|
78
|
+
### New Files Created
|
|
79
|
+
- src/modules/xxx/widgets/useXxxWidgets.ts
|
|
80
|
+
|
|
81
|
+
### Remaining TypeScript Errors
|
|
82
|
+
{output of final vue-tsc --noEmit, if any}
|
|
83
|
+
```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Synced from framework at commit
|
|
1
|
+
Synced from framework at commit b399787ff on 2026-04-14T15:15:27.844Z
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# useBladeContext (defineBladeContext / injectBladeContext)
|
|
2
|
+
|
|
3
|
+
Exposes blade-level data to descendant widgets, extensions, and nested components via Vue's provide/inject mechanism. This pair of functions eliminates the need for prop drilling when child widgets or extension points need access to the parent blade's entity data, loading flags, or other shared state.
|
|
4
|
+
|
|
5
|
+
The pattern follows a "define once, inject anywhere" approach: the blade component calls `defineBladeContext` during setup, and any descendant (no matter how deeply nested) can call `injectBladeContext` to read that data reactively.
|
|
6
|
+
|
|
7
|
+
## When to Use
|
|
8
|
+
|
|
9
|
+
- Share blade state (current entity, loading flags, disabled state) with child widgets without prop drilling
|
|
10
|
+
- Access parent blade data from an extension or widget component
|
|
11
|
+
- Expose selective fields to widgets (e.g., only the entity ID) via a computed getter
|
|
12
|
+
- When NOT to use: for cross-blade communication between sibling blades (use `useBlade` / blade messaging instead)
|
|
13
|
+
|
|
14
|
+
## Basic Usage
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// In a blade's <script setup>
|
|
18
|
+
import { defineBladeContext, injectBladeContext } from '@vc-shell/framework';
|
|
19
|
+
|
|
20
|
+
// Provide context — refs/computeds are auto-unwrapped for consumers
|
|
21
|
+
defineBladeContext({ item, disabled, loading });
|
|
22
|
+
|
|
23
|
+
// Or with a computed for selective exposure
|
|
24
|
+
defineBladeContext(computed(() => ({ id: item.value?.id })));
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// In a widget or nested component
|
|
29
|
+
import { injectBladeContext } from '@vc-shell/framework';
|
|
30
|
+
|
|
31
|
+
const ctx = injectBladeContext();
|
|
32
|
+
// Refs are already unwrapped — access values directly, no .value needed
|
|
33
|
+
const entityId = computed(() => ctx.value.id as string);
|
|
34
|
+
const item = computed(() => ctx.value.item as { id: string; name: string });
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## API
|
|
38
|
+
|
|
39
|
+
### defineBladeContext
|
|
40
|
+
|
|
41
|
+
| Parameter | Type | Required | Description |
|
|
42
|
+
|---|---|---|---|
|
|
43
|
+
| `data` | `MaybeRefOrGetter<Record<string, unknown>>` | Yes | Plain object, ref, or getter to expose |
|
|
44
|
+
|
|
45
|
+
Returns `void`. Must be called in the blade's `<script setup>`.
|
|
46
|
+
|
|
47
|
+
### injectBladeContext
|
|
48
|
+
|
|
49
|
+
Takes no parameters. Returns `ComputedRef<Record<string, unknown>>`.
|
|
50
|
+
|
|
51
|
+
Throws `InjectionError` if no ancestor blade has called `defineBladeContext`.
|
|
52
|
+
|
|
53
|
+
## Recipe: Widget Consuming Blade Context
|
|
54
|
+
|
|
55
|
+
A typical pattern is a sidebar widget that needs to load related data based on the current blade entity. The widget does not receive any props from the blade -- it reads the entity ID from the blade context:
|
|
56
|
+
|
|
57
|
+
```vue
|
|
58
|
+
<script setup lang="ts">
|
|
59
|
+
// widgets/RelatedOrdersWidget.vue
|
|
60
|
+
import { computed, watch } from "vue";
|
|
61
|
+
import { injectBladeContext } from "@vc-shell/framework";
|
|
62
|
+
|
|
63
|
+
const ctx = injectBladeContext();
|
|
64
|
+
const customerId = computed(() => ctx.value.id as string | undefined);
|
|
65
|
+
|
|
66
|
+
// Reload orders whenever the customer changes
|
|
67
|
+
watch(customerId, async (id) => {
|
|
68
|
+
if (id) {
|
|
69
|
+
await loadOrders(id);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
</script>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```vue
|
|
76
|
+
<script setup lang="ts">
|
|
77
|
+
// blades/CustomerDetailBlade.vue
|
|
78
|
+
import { ref, computed } from "vue";
|
|
79
|
+
import { defineBladeContext } from "@vc-shell/framework";
|
|
80
|
+
|
|
81
|
+
const customer = ref({ id: "cust-1", name: "Acme Corp" });
|
|
82
|
+
const loading = ref(false);
|
|
83
|
+
|
|
84
|
+
// Expose the customer data to all descendant widgets
|
|
85
|
+
defineBladeContext(computed(() => ({
|
|
86
|
+
id: customer.value?.id,
|
|
87
|
+
name: customer.value?.name,
|
|
88
|
+
loading: loading.value,
|
|
89
|
+
})));
|
|
90
|
+
</script>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Details
|
|
94
|
+
|
|
95
|
+
- **Automatic ref unwrapping**: `defineBladeContext` shallow-unwraps all ref/computed values in the provided object. Consumers get plain values directly (`ctx.value.item` instead of `ctx.value.item.value`). This works reactively — when the source ref changes, the context updates automatically.
|
|
96
|
+
- **Reactivity**: The provided context is always wrapped in a `computed`, so consumers receive a `ComputedRef` regardless of whether the provider passed a plain object, a ref, or a getter. Changes propagate automatically.
|
|
97
|
+
- **Injection key**: Uses `BladeContextKey` from `framework/injection-keys.ts`. This is a framework-level Symbol, so there is no risk of key collision with application code.
|
|
98
|
+
- **Error handling**: `injectBladeContext` throws an `InjectionError` with a descriptive message if called outside a blade component tree. This fails fast during development rather than silently returning `undefined`.
|
|
99
|
+
- **Scope**: The context is scoped to the Vue component subtree. Each blade in the stack has its own context, so nested blades do not leak data upward or sideways.
|
|
100
|
+
|
|
101
|
+
## Tips
|
|
102
|
+
|
|
103
|
+
- Prefer exposing a computed getter rather than the full reactive object when only a subset of fields is needed. This minimizes unnecessary re-renders in consuming widgets.
|
|
104
|
+
- The context value is untyped (`Record<string, unknown>`). Use type assertions or a typed wrapper in your module if you need type safety (e.g., `ctx.value.id as string`).
|
|
105
|
+
- If a blade does not call `defineBladeContext`, any descendant calling `injectBladeContext` will throw. Make sure all blades that host widgets define their context.
|
|
106
|
+
|
|
107
|
+
## Related
|
|
108
|
+
|
|
109
|
+
- `BladeContextKey` in `framework/injection-keys.ts`
|
|
110
|
+
- `useBladeWidgets` -- widgets that consume blade context
|
|
111
|
+
- `useBladeStack` -- manages the blade navigation stack
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# useBladeForm
|
|
2
|
+
|
|
3
|
+
Unified form state management for blades. Replaces manual combination of `useForm` + `useModificationTracker` + `useBeforeUnload` + `onBeforeClose` with a single composable.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { useBladeForm } from "@vc-shell/framework";
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Basic Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
const { item, loadItem, saveItem } = useItemData();
|
|
15
|
+
|
|
16
|
+
const form = useBladeForm({ data: item });
|
|
17
|
+
|
|
18
|
+
onMounted(async () => {
|
|
19
|
+
await loadItem({ id: param.value });
|
|
20
|
+
form.setBaseline(); // snapshot current data as pristine
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Toolbar
|
|
24
|
+
const toolbar = ref<IBladeToolbar[]>([
|
|
25
|
+
{
|
|
26
|
+
id: "save",
|
|
27
|
+
title: "Save",
|
|
28
|
+
icon: "lucide-save",
|
|
29
|
+
disabled: computed(() => !form.canSave.value),
|
|
30
|
+
async clickHandler() {
|
|
31
|
+
await saveItem(item.value);
|
|
32
|
+
form.setBaseline(); // snapshot after save
|
|
33
|
+
callParent("reload");
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
]);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## API
|
|
40
|
+
|
|
41
|
+
### Options
|
|
42
|
+
|
|
43
|
+
| Option | Type | Default | Description |
|
|
44
|
+
|--------|------|---------|-------------|
|
|
45
|
+
| `data` | `Ref<T>` | required | Reactive data object for the form |
|
|
46
|
+
| `canSaveOverride` | `ComputedRef<boolean>` | — | Additional condition for canSave |
|
|
47
|
+
| `autoBeforeClose` | `boolean \| ComputedRef<boolean>` | `true` | Close guard behavior |
|
|
48
|
+
| `autoBeforeUnload` | `boolean` | `true` | Browser tab close guard |
|
|
49
|
+
| `closeConfirmMessage` | `MaybeRefOrGetter<string>` | — | Custom unsaved changes message |
|
|
50
|
+
| `onRevert` | `() => void \| Promise<void>` | — | Custom revert handler |
|
|
51
|
+
|
|
52
|
+
### Returns
|
|
53
|
+
|
|
54
|
+
| Property | Type | Description |
|
|
55
|
+
|----------|------|-------------|
|
|
56
|
+
| `setBaseline()` | `() => void` | Snapshot current data as pristine. Call after load and after save |
|
|
57
|
+
| `markReady()` | `() => void` | Mark form ready without resetting pristine snapshot. Computes modification state from current data vs setup-time snapshot |
|
|
58
|
+
| `revert()` | `() => void \| Promise<void>` | Revert data to pristine (or call onRevert) |
|
|
59
|
+
| `canSave` | `ComputedRef<boolean>` | `isReady && valid && modified && canSaveOverride` |
|
|
60
|
+
| `isModified` | `ComputedRef<boolean>` | Data differs from pristine (false until setBaseline) |
|
|
61
|
+
| `isReady` | `ComputedRef<boolean>` | setBaseline() called at least once |
|
|
62
|
+
| `formMeta` | vee-validate meta | Form validation state |
|
|
63
|
+
| `setFieldError` | function | Set field error programmatically |
|
|
64
|
+
| `errorBag` | Ref | All field errors |
|
|
65
|
+
|
|
66
|
+
## Lifecycle
|
|
67
|
+
|
|
68
|
+
### Standard (edit existing entity)
|
|
69
|
+
```
|
|
70
|
+
Mount → Load data → setBaseline() → User edits → Save → setBaseline()
|
|
71
|
+
└→ Cancel → revert()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Pre-filled (create from template)
|
|
75
|
+
```
|
|
76
|
+
Mount → Pre-fill data → markReady() → canSave = true immediately
|
|
77
|
+
└→ Save → setBaseline()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## VcBlade Integration
|
|
81
|
+
|
|
82
|
+
`useBladeForm` auto-provides form state to `VcBlade` via inject. No need to pass `:modified` prop:
|
|
83
|
+
|
|
84
|
+
```vue
|
|
85
|
+
<!-- Before -->
|
|
86
|
+
<VcBlade :modified="isModified" :toolbar-items="toolbar">
|
|
87
|
+
|
|
88
|
+
<!-- After -->
|
|
89
|
+
<VcBlade :toolbar-items="toolbar">
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Advanced: Readonly Blade
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const disabled = computed(() => !!param.value && !item.value?.canBeModified);
|
|
96
|
+
|
|
97
|
+
const form = useBladeForm({
|
|
98
|
+
data: item,
|
|
99
|
+
canSaveOverride: computed(() => !disabled.value),
|
|
100
|
+
autoBeforeClose: computed(() => !disabled.value), // no prompt when readonly
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Advanced: Custom Revert
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const form = useBladeForm({
|
|
108
|
+
data: item,
|
|
109
|
+
onRevert: () => loadItem({ id: param.value }), // reload from server
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Advanced: Pre-filled Entity (markReady)
|
|
114
|
+
|
|
115
|
+
When creating a new entity that is pre-populated from a parent (e.g. new offer from a product), the form should be immediately saveable. Use `markReady()` instead of `setBaseline()`:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const form = useBladeForm({ data: item });
|
|
119
|
+
|
|
120
|
+
onMounted(async () => {
|
|
121
|
+
// Populate base fields
|
|
122
|
+
item.value.sku = generateSku();
|
|
123
|
+
await addEmptyInventory();
|
|
124
|
+
|
|
125
|
+
const hasTemplate = !param.value && !!options.value?.templateId;
|
|
126
|
+
|
|
127
|
+
if (hasTemplate) {
|
|
128
|
+
// Fill from template — data diverges from the setup-time snapshot
|
|
129
|
+
await fillFromTemplate(options.value.templateId);
|
|
130
|
+
// Mark ready: compares current data to setup-time snapshot → isModified = true
|
|
131
|
+
form.markReady();
|
|
132
|
+
} else {
|
|
133
|
+
// Standard load — current state becomes the pristine baseline
|
|
134
|
+
await loadItem({ id: param.value });
|
|
135
|
+
form.setBaseline();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### setBaseline vs markReady
|
|
141
|
+
|
|
142
|
+
| | `setBaseline()` | `markReady()` |
|
|
143
|
+
|--|-----------------|---------------|
|
|
144
|
+
| Sets `isReady` | yes | yes |
|
|
145
|
+
| Updates pristine snapshot | yes (current data → pristine) | no (keeps setup-time snapshot) |
|
|
146
|
+
| `trackerIsModified` after call | `false` | computed: `data !== pristineSnapshot` |
|
|
147
|
+
| Use case | Load / Save — "this is the clean state" | Pre-fill — "form is ready, changes are intentional" |
|
|
148
|
+
|
|
149
|
+
### How it works
|
|
150
|
+
|
|
151
|
+
At composable creation, `useBladeForm` takes a snapshot of `data` (the **setup-time snapshot**). When `markReady()` is called:
|
|
152
|
+
|
|
153
|
+
1. `isReady` → `true` (gates `canSave` and the deep watcher)
|
|
154
|
+
2. `trackerIsModified` = `!semanticEqual(data, setupTimeSnapshot)` — since data was mutated during `onMounted`, this evaluates to `true`
|
|
155
|
+
3. Subsequent edits are tracked normally by the deep watcher
|
|
156
|
+
|
|
157
|
+
After save, call `setBaseline()` as usual to capture the saved state as the new pristine snapshot.
|
|
158
|
+
|
|
159
|
+
## Constraints
|
|
160
|
+
|
|
161
|
+
- **Must be called from component `setup()`** (or `<script setup>`). Do NOT call from shared data-composables.
|
|
162
|
+
- Validation rules stay in template (`<Field rules="...">`).
|
|
163
|
+
- `setBaseline()` must be called after data is loaded — before that, `canSave` and `isModified` are `false`.
|
|
164
|
+
|
|
165
|
+
## Migration
|
|
166
|
+
|
|
167
|
+
See `MIGRATION_GUIDE.md` for step-by-step instructions on migrating existing modules.
|