@vc-shell/vc-app-skill 2.8.0-pr399.6ff7b99 → 2.9.0-pr400.2a5fffb

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vc-shell/vc-app-skill",
3
- "version": "2.8.0-pr399.6ff7b99",
3
+ "version": "2.9.0-pr400.2a5fffb",
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.8.0
1
+ 2.9.0
@@ -1 +1 @@
1
- Synced from framework at commit b57bc2f71 on 2026-10-30T15:31:36.432Z
1
+ Synced from framework at commit fd1e671ba on 2026-11-02T09:58:34.572Z
@@ -99,6 +99,32 @@ defineBladeContext(
99
99
  </script>
100
100
  ```
101
101
 
102
+ ## Recipe: Consumers Registered from Another Module
103
+
104
+ Toolbar buttons and headless widgets registered by blade name from another module are plain objects, so `injectBladeContext()` has no component to run in. The framework hands them the context instead:
105
+
106
+ | Registration | Receives the context |
107
+ | ----------------------------------------------------- | --------------------------------------------------------------------------------- |
108
+ | `registerExternalToolbarItem` / `registerToolbarItem` | `clickHandler(ctx)`, `disabled: (ctx) => …`, `isVisible: (blade, ctx) => …` |
109
+ | `registerExternalWidget` / `registerWidget` | `isVisible: (blade, ctx) => …`; the component itself calls `injectBladeContext()` |
110
+ | `registerWidget` with `headless` | `headless.onClick(ctx)`, `headless.disabled: (ctx) => …` |
111
+
112
+ ```typescript
113
+ // In the blade
114
+ defineBladeContext({ selectedItems });
115
+
116
+ // In another module
117
+ registerExternalToolbarItem({
118
+ id: "bulk-export",
119
+ title: "Export selected",
120
+ targetBlades: ["Products"],
121
+ disabled: (ctx) => !(ctx.selectedItems as Product[] | undefined)?.length,
122
+ clickHandler: (ctx) => exportProducts(ctx?.selectedItems as Product[]),
123
+ });
124
+ ```
125
+
126
+ The values are the same unwrapped snapshot `injectBladeContext()` returns, and the state is reactive: `disabled` and `isVisible` follow the blade's refs. A blade without `defineBladeContext` gives these handlers `{}`, not an error.
127
+
102
128
  ## Details
103
129
 
104
130
  - **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.
@@ -197,6 +197,27 @@ export default defineAppModule({
197
197
 
198
198
  The name match is case-insensitive. If the blade registers its own item with the same `id`, the blade's item wins. `registerToolbarItem(item, "Products")` does the same for a single blade.
199
199
 
200
+ ### Reading the blade's data
201
+
202
+ A button registered from outside cannot call `injectBladeContext()`: its handlers run outside the blade's component tree. The framework passes them the data the blade exposed with [`defineBladeContext`](../bladeContext/) instead. `clickHandler` receives it, and so do the function forms of `disabled` and `isVisible`:
203
+
204
+ ```typescript
205
+ // In the target blade
206
+ defineBladeContext({ selectedItems });
207
+
208
+ // In the other module
209
+ registerExternalToolbarItem({
210
+ id: "bulk-export",
211
+ title: "Export selected",
212
+ icon: "lucide-download",
213
+ targetBlades: ["Products"],
214
+ disabled: (ctx) => !(ctx.selectedItems as Product[] | undefined)?.length,
215
+ clickHandler: (ctx) => exportProducts(ctx?.selectedItems as Product[]),
216
+ });
217
+ ```
218
+
219
+ Refs are already unwrapped, and the state follows the blade: the button enables as soon as the user selects a row. A keyboard `shortcut` on the item gets the same context. When the blade defines no context, the handlers get `{}`.
220
+
200
221
  ## Auto-cleanup control
201
222
 
202
223
  By default, items registered through `useToolbar()` inside a component setup are cleared when that component unmounts (`autoCleanup: true`). Disable this for items that should persist beyond the registering component:
@@ -378,19 +399,19 @@ function helperFunction() {
378
399
 
379
400
  ### IToolbarItem
380
401
 
381
- | Property | Type | Required | Description |
382
- | -------------- | ----------------------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
383
- | `id` | `string` | Yes | Unique identifier for the button |
384
- | `title` | `string \| Ref<string> \| ComputedRef<string>` | No | Button label (supports reactive values) |
385
- | `icon` | `string \| (() => string)` | No | Icon class (e.g., `"fas fa-save"`) or a function returning one |
386
- | `clickHandler` | `(app?) => void` | No | Click callback |
387
- | `disabled` | `boolean \| ComputedRef<boolean \| undefined>` | No | Whether the button is disabled |
388
- | `isVisible` | `boolean \| Ref<boolean \| undefined> \| ComputedRef<boolean \| undefined> \| ((blade?) => boolean \| undefined)` | No | Controls button visibility |
389
- | `priority` | `number` | No | Sort order (higher = displayed first, default `0`) |
390
- | `separator` | `"left" \| "right" \| "both"` | No | Adds a visual divider next to the button |
391
- | `permissions` | `string \| string[]` | No | Required permission(s) to display the button |
392
- | `bladeId` | `string` | No | Target blade ID (auto-resolved from context) |
393
- | `shortcut` | `ShortcutDefinition` | No | Keyboard shortcut that triggers `clickHandler`; build with `hotkey.*`. See [useKeyboardShortcuts](../useKeyboardShortcuts/) |
402
+ | Property | Type | Required | Description |
403
+ | -------------- | ------------------------------------------------------------------------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |
404
+ | `id` | `string` | Yes | Unique identifier for the button |
405
+ | `title` | `string \| Ref<string> \| ComputedRef<string>` | No | Button label (supports reactive values) |
406
+ | `icon` | `string \| (() => string)` | No | Icon class (e.g., `"fas fa-save"`) or a function returning one |
407
+ | `clickHandler` | `(context?: BladeContext) => void` | No | Click callback; receives the blade context |
408
+ | `disabled` | `boolean \| ComputedRef<boolean \| undefined> \| ((context: BladeContext) => boolean \| undefined)` | No | Whether the button is disabled; a function receives the blade context |
409
+ | `isVisible` | `boolean \| Ref<boolean \| undefined> \| ComputedRef<boolean \| undefined> \| ((blade, context) => boolean \| undefined)` | No | Controls button visibility; a function receives the blade descriptor and the blade context |
410
+ | `priority` | `number` | No | Sort order (higher = displayed first, default `0`) |
411
+ | `separator` | `"left" \| "right" \| "both"` | No | Adds a visual divider next to the button |
412
+ | `permissions` | `string \| string[]` | No | Required permission(s) to display the button |
413
+ | `bladeId` | `string` | No | Target blade ID (auto-resolved from context) |
414
+ | `shortcut` | `ShortcutDefinition` | No | Keyboard shortcut that triggers `clickHandler`; build with `hotkey.*`. See [useKeyboardShortcuts](../useKeyboardShortcuts/) |
394
415
 
395
416
  `IToolbarItem` is the shape consumed by `ToolbarService`. The blade-level array binding uses `IBladeToolbar` (see [Core types](../../types/)), a near-identical shape that the framework normalizes into `IToolbarItem` before render.
396
417
 
@@ -118,6 +118,35 @@ export default {
118
118
  };
119
119
  ```
120
120
 
121
+ The widget component reads the blade's data with [`injectBladeContext()`](../bladeContext/), since it renders inside the blade. `isVisible` runs before the component exists, so a function form receives the blade context as its second argument:
122
+
123
+ ```typescript
124
+ registerExternalWidget({
125
+ id: "shipping-tracker",
126
+ targetBlades: ["OrderDetails"],
127
+ component: markRaw(ShippingTracker),
128
+ // the blade calls defineBladeContext({ item })
129
+ isVisible: (blade, ctx) => (ctx.item as Order | undefined)?.status === "Shipped",
130
+ });
131
+ ```
132
+
133
+ A headless widget registered by blade name has no component to inject from, so its `onClick` and a function `disabled` receive the context too:
134
+
135
+ ```typescript
136
+ registerWidget(
137
+ {
138
+ id: "invoice",
139
+ title: "Invoice",
140
+ headless: {
141
+ icon: "lucide-receipt",
142
+ disabled: (ctx) => !ctx.item,
143
+ onClick: (ctx) => openInvoice((ctx?.item as Order).id),
144
+ },
145
+ },
146
+ "OrderDetails",
147
+ );
148
+ ```
149
+
121
150
  ## Recipe: Listing All Widgets for a Blade
122
151
 
123
152
  ```vue