@vc-shell/vc-app-skill 2.0.0-alpha.32 → 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.
Files changed (36) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/package.json +1 -1
  3. package/runtime/VERSION +1 -1
  4. package/runtime/agents/migration-agent.md +83 -0
  5. package/runtime/knowledge/docs/_BUILD_HASH.md +1 -1
  6. package/runtime/knowledge/docs/core/composables/useBladeForm/useBladeForm.docs.md +55 -1
  7. package/runtime/knowledge/docs/shell/auth/ChangePasswordPage/change-password-page.docs.md +102 -0
  8. package/runtime/knowledge/docs/ui/components/atoms/vc-card/vc-card.docs.md +4 -0
  9. package/runtime/knowledge/docs/ui/components/molecules/vc-accordion/vc-accordion.docs.md +4 -0
  10. package/runtime/knowledge/docs/ui/components/molecules/vc-checkbox/vc-checkbox.docs.md +5 -0
  11. package/runtime/knowledge/docs/ui/components/molecules/vc-checkbox-group/vc-checkbox-group.docs.md +5 -0
  12. package/runtime/knowledge/docs/ui/components/molecules/vc-color-input/vc-color-input.docs.md +5 -0
  13. package/runtime/knowledge/docs/ui/components/molecules/vc-date-picker/vc-date-picker.docs.md +7 -0
  14. package/runtime/knowledge/docs/ui/components/molecules/vc-editor/vc-editor.docs.md +5 -0
  15. package/runtime/knowledge/docs/ui/components/molecules/vc-field/vc-field.docs.md +5 -0
  16. package/runtime/knowledge/docs/ui/components/molecules/vc-file-upload/vc-file-upload.docs.md +5 -0
  17. package/runtime/knowledge/docs/ui/components/molecules/vc-input/vc-input.docs.md +7 -0
  18. package/runtime/knowledge/docs/ui/components/molecules/vc-input-currency/vc-input-currency.docs.md +7 -0
  19. package/runtime/knowledge/docs/ui/components/molecules/vc-input-dropdown/vc-input-dropdown.docs.md +7 -0
  20. package/runtime/knowledge/docs/ui/components/molecules/vc-multivalue/vc-multivalue.docs.md +7 -0
  21. package/runtime/knowledge/docs/ui/components/molecules/vc-radio-button/vc-radio-button.docs.md +5 -0
  22. package/runtime/knowledge/docs/ui/components/molecules/vc-radio-group/vc-radio-group.docs.md +5 -0
  23. package/runtime/knowledge/docs/ui/components/molecules/vc-rating/vc-rating.docs.md +5 -0
  24. package/runtime/knowledge/docs/ui/components/molecules/vc-select/vc-select.docs.md +7 -0
  25. package/runtime/knowledge/docs/ui/components/molecules/vc-slider/vc-slider.docs.md +5 -0
  26. package/runtime/knowledge/docs/ui/components/molecules/vc-switch/vc-switch.docs.md +5 -0
  27. package/runtime/knowledge/docs/ui/components/molecules/vc-textarea/vc-textarea.docs.md +7 -0
  28. package/runtime/knowledge/docs/ui/components/organisms/vc-blade/vc-blade.docs.md +30 -0
  29. package/runtime/knowledge/docs/ui/components/organisms/vc-data-table/vc-data-table.docs.md +28 -0
  30. package/runtime/knowledge/migration-prompts/blade-form-migration.md +246 -0
  31. package/runtime/knowledge/migration-prompts/blade-props-migration.md +195 -0
  32. package/runtime/knowledge/migration-prompts/notifications-migration.md +218 -0
  33. package/runtime/knowledge/migration-prompts/nswag-migration.md +248 -0
  34. package/runtime/knowledge/migration-prompts/widgets-migration.md +157 -0
  35. package/runtime/vc-app.md +126 -0
  36. package/runtime/knowledge/docs/core/constants/constants.docs.md +0 -185
package/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
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))
1
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)
2
17
 
3
18
  **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.32",
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.32
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 fc29b1e1b on 2026-04-02T05:22:47.427Z
1
+ Synced from framework at commit b399787ff on 2026-04-14T15:15:27.844Z
@@ -54,6 +54,7 @@ const toolbar = ref<IBladeToolbar[]>([
54
54
  | Property | Type | Description |
55
55
  |----------|------|-------------|
56
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 |
57
58
  | `revert()` | `() => void \| Promise<void>` | Revert data to pristine (or call onRevert) |
58
59
  | `canSave` | `ComputedRef<boolean>` | `isReady && valid && modified && canSaveOverride` |
59
60
  | `isModified` | `ComputedRef<boolean>` | Data differs from pristine (false until setBaseline) |
@@ -64,9 +65,16 @@ const toolbar = ref<IBladeToolbar[]>([
64
65
 
65
66
  ## Lifecycle
66
67
 
68
+ ### Standard (edit existing entity)
67
69
  ```
68
70
  Mount → Load data → setBaseline() → User edits → Save → setBaseline()
69
- └→ Cancel → revert()
71
+ └→ Cancel → revert()
72
+ ```
73
+
74
+ ### Pre-filled (create from template)
75
+ ```
76
+ Mount → Pre-fill data → markReady() → canSave = true immediately
77
+ └→ Save → setBaseline()
70
78
  ```
71
79
 
72
80
  ## VcBlade Integration
@@ -102,6 +110,52 @@ const form = useBladeForm({
102
110
  });
103
111
  ```
104
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
+
105
159
  ## Constraints
106
160
 
107
161
  - **Must be called from component `setup()`** (or `<script setup>`). Do NOT call from shared data-composables.
@@ -0,0 +1,102 @@
1
+ # ChangePasswordPage
2
+
3
+ Change password page with current, new, and confirm password fields. Supports a `forced` mode for expired passwords that displays an info banner and is triggered by post-login redirect. This full-page variant is used when the user must change their password before accessing the application (e.g., expired password policy). For voluntary password changes from within the app, the `ChangePasswordButton` in the settings menu opens a popup instead.
4
+
5
+ ## When to Use
6
+
7
+ - When a signed-in user wants to change their password (full-page flow)
8
+ - In `forced` mode after login when the user's password has expired
9
+ - The standard vc-shell routing maps `/change-password` to this page
10
+
11
+ ## Basic Usage
12
+
13
+ ```vue
14
+ <template>
15
+ <ChangePassword />
16
+ </template>
17
+
18
+ <!-- Forced mode (expired password) -->
19
+ <template>
20
+ <ChangePassword forced />
21
+ </template>
22
+ ```
23
+
24
+ With custom branding:
25
+
26
+ ```vue
27
+ <template>
28
+ <ChangePassword
29
+ forced
30
+ logo="/assets/my-company-logo.svg"
31
+ background="/assets/custom-background.jpg"
32
+ />
33
+ </template>
34
+ ```
35
+
36
+ ## Key Props
37
+
38
+ | Prop | Type | Default | Description |
39
+ |------|------|---------|-------------|
40
+ | `forced` | `boolean` | `false` | Show expired-password info banner and adjusted title |
41
+ | `logo` | `string` | - | Override logo image URL |
42
+ | `background` | `string` | - | Custom background image URL |
43
+
44
+ ## Recipe: Router Configuration with Forced Mode
45
+
46
+ Set up the route so the login page can redirect here when the password is expired:
47
+
48
+ ```ts
49
+ import ChangePassword from "@vc-shell/framework/shared/pages/ChangePasswordPage";
50
+
51
+ const routes = [
52
+ {
53
+ path: "/change-password",
54
+ name: "ChangePassword",
55
+ component: ChangePassword,
56
+ props: (route) => ({
57
+ forced: route.query.forced === "true",
58
+ }),
59
+ },
60
+ ];
61
+ ```
62
+
63
+ In the login flow, redirect when the user's password has expired:
64
+
65
+ ```ts
66
+ async function handleLogin() {
67
+ const result = await signIn(username.value, password.value);
68
+ if (result.passwordExpired) {
69
+ router.push({ name: "ChangePassword", query: { forced: "true" } });
70
+ } else {
71
+ router.push("/");
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## Features
77
+
78
+ - **Real-time password policy validation**: Uses `useUserManagement().validatePassword` to check the new password against the platform's policy as the user types (minimum length, required uppercase, lowercase, digits, special characters)
79
+ - **Equal password detection**: Shows a specific "Equal-passwords" error when the new password matches the current password, without making an API call
80
+ - **Confirm-password mismatch detection**: Validates that the new password and confirm password fields match
81
+ - **Forced mode banner**: When `forced` is `true`, displays an info banner explaining that the password has expired and must be changed
82
+ - **Cancel behavior**: Cancel button signs out the user and redirects to `/login`
83
+ - **Success redirect**: On successful password change, redirects to `/` (main application)
84
+
85
+ ## Details
86
+
87
+ - **Auth layout**: Renders inside `VcAuthLayout`, providing the centered card design with logo and background.
88
+ - **Password policy**: The validation rules are fetched from the platform API and include configurable requirements (minimum length, character classes, etc.). The component displays these rules as a checklist.
89
+ - **Forced vs voluntary**: In forced mode (`forced=true`), the page title changes to reflect the expired password scenario, and an info banner explains why the change is required. The form fields and behavior are otherwise identical.
90
+ - **Sign-out on cancel**: If the user cancels during a forced password change, they are signed out. This prevents access to the application with an expired password.
91
+
92
+ ## Tips
93
+
94
+ - The `forced` prop is typically set via a route query parameter, not hardcoded. The login page detects expired passwords and redirects with the appropriate flag.
95
+ - Password policy validation runs on keyup, providing immediate feedback. The submit button is disabled until all policy requirements are met and the passwords match.
96
+ - This page is distinct from the `ChangePasswordButton` popup. The page is for full-screen flow (forced changes), while the popup is for voluntary in-app password changes.
97
+
98
+ ## Related Components
99
+
100
+ - **VcAuthLayout** - The underlying centered card layout
101
+ - **LoginPage** - Redirects here when `user.passwordExpired` is true
102
+ - **ChangePasswordButton** - Settings menu popup variant for voluntary password changes
@@ -351,3 +351,7 @@ const validationHeader = computed(() =>
351
351
  - [VcContainer](../vc-container/) -- scrollable content wrapper without header or collapsing
352
352
  - [VcBanner](../vc-banner/) -- for alert/notification messages rather than content grouping
353
353
  - [VcCol](../vc-col/) / [VcRow](../vc-row/) -- for grid-based layout within card bodies
354
+
355
+ ## Skeleton / Loading State
356
+
357
+ When placed inside a `VcBlade` with `loading=true`, VcCard shows a skeleton header (if the `header` prop is set) while its body content renders normally — child components self-skeletonize via their own `BladeLoadingKey` injection.
@@ -250,3 +250,7 @@ interface AccordionItem {
250
250
  ## Related Components
251
251
 
252
252
  - [VcAccordionItem](./_internal/vc-accordion-item/) -- individual accordion panel (used internally and available via the default slot)
253
+
254
+ ## Skeleton / Loading State
255
+
256
+ When placed inside a `VcBlade` with `loading=true`, the component automatically renders a skeleton placeholder matching its visual footprint. No additional props or configuration needed.
@@ -323,3 +323,8 @@ const selected = ref<string[]>([]);
323
323
  - [VcSwitch](../vc-switch/) -- toggle switch for on/off settings
324
324
  - [VcRadioButton](../vc-radio-button/) -- mutually exclusive single selection
325
325
  - [VcInputGroup](../vc-input-group/) -- semantic wrapper for grouping checkboxes or radio buttons
326
+
327
+ ## Skeleton / Loading State
328
+
329
+ When placed inside a `VcBlade` with `loading=true`, the component renders a skeleton placeholder matching its shape — a control indicator and label block. No configuration needed.
330
+
@@ -144,3 +144,8 @@ interface CheckboxGroupOption<V = string | number | boolean> {
144
144
  - [VcCheckbox](../vc-checkbox/) — individual checkbox component
145
145
  - [VcRadioGroup](../vc-radio-group/) — mutually exclusive option group
146
146
  - [VcInputGroup](../vc-input-group/) — generic form field group (used internally)
147
+
148
+ ## Skeleton / Loading State
149
+
150
+ When placed inside a `VcBlade` with `loading=true`, the component renders a skeleton placeholder matching its shape — a control indicator and label block. No configuration needed.
151
+
@@ -151,3 +151,8 @@ The native color picker does not support alpha/transparency. If you need RGBA co
151
151
 
152
152
  - [VcInput](../vc-input/) -- general-purpose input (delegates to VcColorInput for `type="color"`)
153
153
  - [VcField](../vc-field/) -- read-only field display (for showing a color value without editing)
154
+
155
+ ## Skeleton / Loading State
156
+
157
+ When placed inside a `VcBlade` with `loading=true`, the component automatically renders a skeleton placeholder matching its visual footprint. No additional props or configuration needed.
158
+
@@ -359,3 +359,10 @@ Uses the same `--input-*` variables as VcInput for consistent styling across all
359
359
 
360
360
  - [VcInput](../vc-input/) -- general-purpose input; delegates to VcDatePicker for `type="date"` and `type="datetime-local"`
361
361
  - [VcMultivalue](../vc-multivalue/) -- can handle multiple date values with `type="date"`
362
+
363
+ ## Skeleton / Loading State
364
+
365
+ When placed inside a `VcBlade` with `loading=true`, the component automatically renders a skeleton placeholder matching its visual footprint — a label block (when the `label` prop is set) and an input-shaped block. No additional props or configuration needed.
366
+
367
+ This behavior is powered by `BladeLoadingKey` via Vue's provide/inject. The component injects the loading state from the nearest `VcBlade` ancestor.
368
+
@@ -298,3 +298,8 @@ const content = ref("<h1>Title</h1>");
298
298
 
299
299
  - [VcTextarea](../vc-textarea/) -- plain multi-line text input (no formatting)
300
300
  - [VcInput](../vc-input/) -- single-line text input
301
+
302
+ ## Skeleton / Loading State
303
+
304
+ When placed inside a `VcBlade` with `loading=true`, the component automatically renders a skeleton placeholder matching its visual footprint. No additional props or configuration needed.
305
+
@@ -166,3 +166,8 @@ The `type` prop affects rendering, not validation. Setting `type="email"` does n
166
166
  - [VcInput](../vc-input/) -- editable text field (use instead when user input is needed)
167
167
  - [VcLabel](../../atoms/vc-label/) -- standalone label atom used internally
168
168
  - [VcCol](../../atoms/vc-col/) -- column layout atom used for aspect ratio
169
+
170
+ ## Skeleton / Loading State
171
+
172
+ When placed inside a `VcBlade` with `loading=true`, the component automatically renders a skeleton placeholder matching its visual footprint. No additional props or configuration needed.
173
+
@@ -322,3 +322,8 @@ async function onUpload(files: FileList) {
322
322
  - [VcGallery](../../organisms/vc-gallery/) -- Full image gallery with preview, reorder, drag-and-drop sorting, and upload
323
323
  - [VcImageTile](../vc-image-tile/) -- Image display tile used inside galleries
324
324
  - [VcHint](../../atoms/vc-hint/) -- Used internally for error message display
325
+
326
+ ## Skeleton / Loading State
327
+
328
+ When placed inside a `VcBlade` with `loading=true`, the component automatically renders a skeleton placeholder matching its visual footprint. No additional props or configuration needed.
329
+
@@ -786,3 +786,10 @@ VcInput follows WAI-ARIA best practices for form fields:
786
786
  - [VcInputGroup](../vc-input-group/) -- Groups multiple inputs with shared label, error state, and disabled state
787
787
  - [VcLabel](../../atoms/vc-label/) -- The label atom used internally by VcInput
788
788
  - [VcHint](../../atoms/vc-hint/) -- The hint/error atom used internally by VcInput
789
+
790
+ ## Skeleton / Loading State
791
+
792
+ When placed inside a `VcBlade` with `loading=true`, the component automatically renders a skeleton placeholder matching its visual footprint — a label block (when the `label` prop is set) and an input-shaped block. No additional props or configuration needed.
793
+
794
+ This behavior is powered by `BladeLoadingKey` via Vue's provide/inject. The component injects the loading state from the nearest `VcBlade` ancestor.
795
+
@@ -368,3 +368,10 @@ Additionally inherits all `--input-*` CSS variables from VcInput/VcInputDropdown
368
368
  - [VcInputDropdown](../vc-input-dropdown/) -- the underlying composite input + dropdown component
369
369
  - [VcInput](../vc-input/) -- plain input for non-currency numbers
370
370
  - [VcSelect](../vc-select/) -- standalone dropdown selection
371
+
372
+ ## Skeleton / Loading State
373
+
374
+ When placed inside a `VcBlade` with `loading=true`, the component automatically renders a skeleton placeholder matching its visual footprint — a label block (when the `label` prop is set) and an input-shaped block. No additional props or configuration needed.
375
+
376
+ This behavior is powered by `BladeLoadingKey` via Vue's provide/inject. The component injects the loading state from the nearest `VcBlade` ancestor.
377
+
@@ -305,3 +305,10 @@ Replace the default dropdown toggle with a custom element using the `button` slo
305
305
  - [VcInputCurrency](../vc-input-currency/) -- Currency-specific variant with built-in locale formatting
306
306
  - [VcInput](../vc-input/) -- Standalone text input (used internally)
307
307
  - [VcSelect](../vc-select/) -- Standalone dropdown selection (used internally)
308
+
309
+ ## Skeleton / Loading State
310
+
311
+ When placed inside a `VcBlade` with `loading=true`, the component automatically renders a skeleton placeholder matching its visual footprint — a label block (when the `label` prop is set) and an input-shaped block. No additional props or configuration needed.
312
+
313
+ This behavior is powered by `BladeLoadingKey` via Vue's provide/inject. The component injects the loading state from the nearest `VcBlade` ancestor.
314
+
@@ -345,3 +345,10 @@ The component uses `--multivalue-*` variables that fall back to `--select-*` tok
345
345
  - [VcSelect](../vc-select/) -- dropdown for single/multi selection without manual entry
346
346
  - [VcInput](../vc-input/) -- single-value text input
347
347
  - [VcInputGroup](../vc-input-group/) -- semantic wrapper for grouping form controls
348
+
349
+ ## Skeleton / Loading State
350
+
351
+ When placed inside a `VcBlade` with `loading=true`, the component automatically renders a skeleton placeholder matching its visual footprint — a label block (when the `label` prop is set) and an input-shaped block. No additional props or configuration needed.
352
+
353
+ This behavior is powered by `BladeLoadingKey` via Vue's provide/inject. The component injects the loading state from the nearest `VcBlade` ancestor.
354
+
@@ -161,3 +161,8 @@ Do not mix `binary` mode with regular `value` comparison in the same group. Bina
161
161
  - [VcSwitch](../vc-switch/) -- for on/off toggles
162
162
  - [VcInputGroup](../vc-input-group/) -- semantic wrapper with `role="radiogroup"`
163
163
  - [VcSelect](../vc-select/) -- dropdown for larger option sets
164
+
165
+ ## Skeleton / Loading State
166
+
167
+ When placed inside a `VcBlade` with `loading=true`, the component renders a skeleton placeholder matching its shape — a control indicator and label block. No configuration needed.
168
+
@@ -144,3 +144,8 @@ interface RadioGroupOption<V = string | number | boolean> {
144
144
  - [VcRadioButton](../vc-radio-button/) — individual radio button component
145
145
  - [VcCheckboxGroup](../vc-checkbox-group/) — multiple-selection option group
146
146
  - [VcInputGroup](../vc-input-group/) — generic form field group (used internally)
147
+
148
+ ## Skeleton / Loading State
149
+
150
+ When placed inside a `VcBlade` with `loading=true`, the component renders a skeleton placeholder matching its shape — a control indicator and label block. No configuration needed.
151
+
@@ -251,3 +251,8 @@ Use the `"star-and-text"` or `"text"` variant in table cell slots for a compact
251
251
  - [VcField](../vc-field/) -- Read-only field display, often used alongside ratings in detail views
252
252
  - [VcLabel](../../atoms/vc-label/) -- Label atom used internally for the label and tooltip
253
253
  - [VcIcon](../../atoms/vc-icon/) -- Renders the star icons internally
254
+
255
+ ## Skeleton / Loading State
256
+
257
+ When placed inside a `VcBlade` with `loading=true`, the component automatically renders a skeleton placeholder matching its visual footprint. No additional props or configuration needed.
258
+
@@ -682,3 +682,10 @@ Don't forget `:key` with cascading selects, otherwise the second select will ret
682
682
  - [VcInput](../vc-input/) — simple text field
683
683
  - [VcDatePicker](../vc-date-picker/) — date selection
684
684
  - [VcField](../vc-field/) — wrapper with label/error/hint (read-only display)
685
+
686
+ ## Skeleton / Loading State
687
+
688
+ When placed inside a `VcBlade` with `loading=true`, the component automatically renders a skeleton placeholder matching its visual footprint — a label block (when the `label` prop is set) and an input-shaped block. No additional props or configuration needed.
689
+
690
+ This behavior is powered by `BladeLoadingKey` via Vue's provide/inject. The component injects the loading state from the nearest `VcBlade` ancestor.
691
+
@@ -113,3 +113,8 @@ const products = [
113
113
  ## Related Components
114
114
 
115
115
  - [VcImage](../../atoms/vc-image/) -- image component often used inside slides
116
+
117
+ ## Skeleton / Loading State
118
+
119
+ When placed inside a `VcBlade` with `loading=true`, the component automatically renders a skeleton placeholder matching its visual footprint. No additional props or configuration needed.
120
+
@@ -298,3 +298,8 @@ const isActive = ref(true);
298
298
  - [VcCheckbox](../vc-checkbox/) -- for checkboxes and checkbox groups
299
299
  - [VcRadioButton](../vc-radio-button/) -- for mutually exclusive choices
300
300
  - [VcInputGroup](../vc-input-group/) -- semantic wrapper for grouping form controls
301
+
302
+ ## Skeleton / Loading State
303
+
304
+ When placed inside a `VcBlade` with `loading=true`, the component renders a skeleton placeholder matching its shape — a control indicator and label block. No configuration needed.
305
+
@@ -305,3 +305,10 @@ const description = ref<string>("");
305
305
  - [VcInput](../vc-input/) -- single-line text input
306
306
  - [VcEditor](../vc-editor/) -- rich text / Markdown editor
307
307
  - [VcInputGroup](../vc-input-group/) -- semantic wrapper for grouping form controls
308
+
309
+ ## Skeleton / Loading State
310
+
311
+ When placed inside a `VcBlade` with `loading=true`, the component automatically renders a skeleton placeholder matching its visual footprint — a label block (when the `label` prop is set) and an input-shaped block. No additional props or configuration needed.
312
+
313
+ This behavior is powered by `BladeLoadingKey` via Vue's provide/inject. The component injects the loading state from the nearest `VcBlade` ancestor.
314
+
@@ -659,3 +659,33 @@ openBlade({ name: "ProductsList" });
659
659
  | `useLoading()` | Merges multiple loading refs into a single boolean. |
660
660
  | `usePopup()` | Confirmation dialogs and error messages. |
661
661
  | `useBladeWidgets()` | Register contextual widgets for the blade widget area. |
662
+
663
+ ## Content Skeleton Mode
664
+
665
+ When `loading=true`, VcBlade provides `BladeLoadingKey` to all descendant components via Vue's provide/inject. Each framework UI component automatically renders a skeleton placeholder matching its visual footprint.
666
+
667
+ ### How It Works
668
+
669
+ 1. Set `loading` prop on VcBlade (typically bound to your data-fetching composable's loading ref)
670
+ 2. All child components (`VcInput`, `VcSelect`, `VcCard`, etc.) detect the loading state and render skeletons
671
+ 3. Layout containers (`VcContainer`, `VcRow`, `VcCol`, `VcForm`) are transparent — they preserve layout structure
672
+ 4. When loading completes, components switch to their normal rendering
673
+
674
+ ### What Shows During Loading
675
+
676
+ - **Config-gated fields** (`v-if="config.showName"`) — config is available immediately, so these fields show skeletons
677
+ - **Data-gated fields** (`v-if="item.productData"`) — data not yet loaded, so these fields don't appear in the skeleton
678
+ - **Header and toolbar** — have their own dedicated skeletons (unchanged)
679
+
680
+ No changes to existing blade pages are required.
681
+
682
+ ### Custom Components
683
+
684
+ To make custom components skeleton-aware:
685
+
686
+ ```ts
687
+ import { useBladeLoading } from "@vc-shell/framework";
688
+
689
+ const bladeLoading = useBladeLoading();
690
+ // bladeLoading.value === true when parent VcBlade is loading
691
+ ```
@@ -619,6 +619,33 @@ Customize expand/collapse icons:
619
619
  </VcDataTable>
620
620
  ```
621
621
 
622
+ ### Conditional expansion
623
+
624
+ Use `isRowExpandable` to control which rows show the expand toggle. Rows that fail the predicate cannot be expanded:
625
+
626
+ ```vue
627
+ <template>
628
+ <VcDataTable
629
+ :items="orders"
630
+ v-model:expanded-rows="expandedRows"
631
+ :is-row-expandable="(order) => order.lineItems.length > 0"
632
+ >
633
+ <VcColumn id="expand" :expander="true" :width="40" />
634
+ <VcColumn id="orderNumber" field="number" title="Order #" />
635
+
636
+ <template #expansion="{ data }">
637
+ <div class="tw-p-4">
638
+ <p v-for="item in data.lineItems" :key="item.id">{{ item.name }}</p>
639
+ </div>
640
+ </template>
641
+ </VcDataTable>
642
+ </template>
643
+
644
+ <script setup lang="ts">
645
+ const expandedRows = ref<Order[]>([]);
646
+ </script>
647
+ ```
648
+
622
649
  ---
623
650
 
624
651
  ## Row Grouping
@@ -1264,6 +1291,7 @@ function onRowRemove(event: { data: Product; index: number; cancel: () => void }
1264
1291
  | `expandedRows` | `T[]` | `[]` | Expanded rows. Use with `v-model:expandedRows`. |
1265
1292
  | `expandedRowIcon` | `string` | `"lucide-chevron-down"` | Icon for expanded state. |
1266
1293
  | `collapsedRowIcon` | `string` | `"lucide-chevron-right"` | Icon for collapsed state. |
1294
+ | `isRowExpandable` | `(data: T) => boolean` | -- | Per-row predicate to hide the expand toggle. |
1267
1295
 
1268
1296
  ### Row Grouping
1269
1297