@vc-shell/vc-app-skill 2.0.6 → 2.0.7-pr237.4b187d7

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.0.6",
3
+ "version": "2.0.7-pr237.4b187d7",
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": [
@@ -26,5 +26,10 @@
26
26
  "publishConfig": {
27
27
  "access": "public",
28
28
  "registry": "https://registry.npmjs.org/"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/VirtoCommerce/vc-shell.git",
33
+ "directory": "cli/vc-app-skill"
29
34
  }
30
- }
35
+ }
package/runtime/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.6
1
+ 2.0.7
@@ -1 +1 @@
1
- Synced from framework at commit a3ddbc29a on 2026-05-25T13:00:53.288Z
1
+ Synced from framework at commit 1fb1bd541 on 2026-06-04T07:51:39.317Z
@@ -136,6 +136,8 @@ The composable holds an **internal ref** (`_items`) that is the source of truth
136
136
 
137
137
  This two-way sync avoids reactivity issues when the source is a `WritableComputed` wrapping deeply nested properties (e.g., `item.value.productData.assets`).
138
138
 
139
+ **`sortOrder` normalization:** items entering from the source are ordered by their current `sortOrder` (items without one stay stable at the end) and reassigned a clean sequential `sortOrder` (`0..n`). This guarantees every item carries a `sortOrder` for display and reordering even when the source provides them without one. The normalization is idempotent, so it does not loop on the `_sync()` writeback.
140
+
139
141
  ## Types
140
142
 
141
143
  ### `AssetLike`
@@ -62,14 +62,14 @@ None.
62
62
 
63
63
  ### Returns (`ILanguageService`)
64
64
 
65
- | Property / Method | Type | Description |
66
- | ------------------------ | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
67
- | `currentLocale` | `ComputedRef<string>` | The currently active locale code (e.g., `"en-US"`, `"de-DE"`). |
68
- | `setLocale` | `(locale: string) => void` | Switches the application locale. This updates `vue-i18n`'s locale and triggers re-rendering of all translated text. |
69
- | `getLocaleByTag` | `(localeTag: string) => string \| undefined` | Resolves a locale tag to its display string (e.g., `"en-US"` to `"English (United States)"`). Returns `undefined` if the tag is not recognized. |
70
- | `resolveCamelCaseLocale` | `(locale: string) => string` | Converts a locale code to camelCase format (e.g., `"en-US"` to `"enUs"`). Useful for dynamic property access on objects keyed by locale. |
71
- | `getFlag` | `(language: string) => Promise<string>` | Fetches a flag image URL for the given language/locale. Returns a promise because flags may be loaded lazily. |
72
- | `getCountryCode` | `(language: string) => string` | Extracts the country code from a language tag (e.g., `"en-US"` to `"US"`, `"de-DE"` to `"DE"`). |
65
+ | Property / Method | Type | Description |
66
+ | ------------------------ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
67
+ | `currentLocale` | `ComputedRef<string>` | The currently active locale code (e.g., `"en-US"`, `"de-DE"`). |
68
+ | `setLocale` | `(locale: string) => void` | Switches the application locale. This updates `vue-i18n`'s locale and triggers re-rendering of all translated text. |
69
+ | `getLocaleByTag` | `(localeTag: string) => string \| undefined` | Resolves a locale tag to its native display name. Regional tags stay distinct (e.g., `"en-US"` `"American English"`, `"en-GB"` → `"British English"`); plain codes resolve to the base native name (`"fr"` → `"Français"`). Returns `undefined` if the tag is not recognized. |
70
+ | `resolveCamelCaseLocale` | `(locale: string) => string` | Converts a locale code to camelCase format (e.g., `"en-US"` to `"enUs"`). Useful for dynamic property access on objects keyed by locale. |
71
+ | `getFlag` | `(language: string) => Promise<string>` | Fetches a flag image URL for the given language/locale. Returns a promise because flags may be loaded lazily. |
72
+ | `getCountryCode` | `(language: string) => string` | Extracts the country code from a language tag (e.g., `"en-US"` to `"US"`, `"de-DE"` to `"DE"`). |
73
73
 
74
74
  ### Additional Exports
75
75
 
@@ -7,16 +7,15 @@ slug: ai-agent
7
7
 
8
8
  # AI Agent Plugin
9
9
 
10
- Integrates an AI assistant panel (chatbot iframe) into the vc-shell application. Provides blade-aware context passing, bidirectional postMessage communication, and preview/apply workflows.
10
+ Integrates an AI assistant panel (chatbot iframe) into the vc-shell application. Provides blade-aware context passing and bidirectional postMessage communication.
11
11
 
12
12
  ## Overview
13
13
 
14
- The AI agent plugin embeds an external chatbot via an iframe panel that slides in from the right side of the application. It automatically sends the current blade context (user, active blade, selected items) to the chatbot and handles incoming commands (navigate, preview changes, download files). The plugin is optional -- if no `APP_AI_AGENT_URL` environment variable or `config.url` is provided, it silently skips installation.
14
+ The AI agent plugin embeds an external chatbot via an iframe panel that slides in from the right side of the application. It automatically sends the current blade context (user, active blade, selected items) to the chatbot and handles incoming commands (navigate, download files). The plugin is optional -- if no `APP_AI_AGENT_URL` environment variable or `config.url` is provided, it silently skips installation.
15
15
 
16
16
  ## When to Use
17
17
 
18
18
  - Embed an AI assistant chatbot panel into your vc-shell application with automatic blade context passing
19
- - Enable preview/apply workflows where AI suggests changes and the user confirms them
20
19
  - When NOT to use: if you don't have an AI agent backend -- the plugin silently skips when no `APP_AI_AGENT_URL` is set
21
20
 
22
21
  ## Installation / Registration
@@ -88,10 +87,7 @@ Binds blade data to the AI agent context. Call this in each blade that should pa
88
87
  | `dataRef` | `Ref<T> \| Ref<T[]>` | Data to send (single object for details, array for list) |
89
88
  | `suggestions` | `ISuggestion[]` | Custom suggestion cards for the chatbot UI |
90
89
 
91
- | Return | Type | Description |
92
- | ---------------------------- | ----------------------- | ------------------------------------------------ |
93
- | `previewState.isActive` | `ComputedRef<boolean>` | Whether AI-suggested changes are being previewed |
94
- | `previewState.changedFields` | `ComputedRef<string[]>` | List of field names with pending changes |
90
+ The composable returns `void`. It wires the watcher and the unmount cleanup; nothing is exposed to the caller.
95
91
 
96
92
  ### PostMessage Protocol
97
93
 
@@ -103,12 +99,9 @@ Binds blade data to the AI agent context. Call this in each blade that should pa
103
99
  **Chatbot to Shell:**
104
100
 
105
101
  - `CHAT_READY` -- Chatbot finished loading
106
- - `NAVIGATE_TO_APP` -- Open a specific blade
107
- - `PREVIEW_CHANGES` -- Preview data changes in the form
108
- - `APPLY_CHANGES` -- Apply confirmed changes
109
- - `RELOAD_BLADE` -- Reload the current blade
110
- - `DOWNLOAD_FILE` -- Download a file (base64)
111
- - `CHAT_ERROR` -- Error from chatbot
102
+ - `NAVIGATE_TO_APP` -- Open a specific blade (driven by markdown action links in assistant messages)
103
+ - `EXPAND_IN_CHAT` -- Expand an item inline in the chat (markdown action link)
104
+ - `SHOW_MORE` -- Request the next page of a result category (markdown action link)
112
105
 
113
106
  ## Usage
114
107
 
@@ -117,7 +110,7 @@ Binds blade data to the AI agent context. Call this in each blade that should pa
117
110
  ```typescript
118
111
  // In a details blade
119
112
  const product = ref<Product>({});
120
- const { previewState } = useAiAgentContext({
113
+ useAiAgentContext({
121
114
  dataRef: product,
122
115
  suggestions: [{ id: "translate", title: "Translate", icon: "translation", prompt: "Translate to English" }],
123
116
  });
@@ -149,17 +142,6 @@ onMessage((message) => {
149
142
  });
150
143
  ```
151
144
 
152
- ### Preview State Visual Feedback
153
-
154
- ```vue
155
- <template>
156
- <VcInput
157
- v-model="product.name"
158
- :class="{ 'ai-preview': previewState.changedFields.value.includes('name') }"
159
- />
160
- </template>
161
- ```
162
-
163
145
  ## Related
164
146
 
165
147
  - `framework/core/plugins/ai-agent/services/ai-agent-service.ts` -- core service factory (`createAiAgentService`)
@@ -130,9 +130,7 @@ Plugins can register components **before** the host declares the extension point
130
130
  2. Later, host calls `defineExtensionPoint("seller:commissions")` -- the store upgrades the entry to "declared" and preserves all previously registered components.
131
131
  3. The host's `components` computed ref reactively picks up the registered components.
132
132
 
133
- This means module load order does not matter. Remote modules loaded via Module Federation may install in any sequence, and extensions still work.
134
-
135
- > **Dev warning:** In development mode, if a plugin registers into a point that is never declared, a console warning appears: `Extension point "xyz" is not declared.` This helps catch typos in extension point names.
133
+ This means module load order does not matter. Remote modules loaded via Module Federation may install in any sequence, and extensions still work. Plugins typically register in module `install()` at app startup, while hosts declare lazily when their page mounts -- an undeclared entry in between is a normal, expected state.
136
134
 
137
135
  ### Priority-Based Sorting
138
136
 
@@ -517,7 +515,7 @@ add({ id: "my-fields", component: MyFields });
517
515
  // Component is registered but never rendered because the host declared "seller:commissions"
518
516
  ```
519
517
 
520
- The dev-mode console warning (`Extension point "seller:comissions" is not declared`) will alert you to this. Always check the console in development.
518
+ There is no console warning for this case -- registering before a host declares the point is a supported flow, so the framework cannot tell a typo apart from a host page that simply has not been opened yet. The most reliable protection is to avoid string literals altogether:
521
519
 
522
520
  > **Tip:** Define constants for your extension point names in a shared file:
523
521
  >
@@ -37,7 +37,7 @@ The composables follow a PrimeVue-inspired pattern: each returns reactive state
37
37
 
38
38
  | Composable | Purpose |
39
39
  | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
40
- | `useTableRowReorder` | Drag-and-drop row reordering with live-swap at 50% vertical threshold. Commits via `dragend` (always fires) with `drop` as preferred path. |
40
+ | `useTableRowReorder` | Drag-and-drop row reordering via SortableJS (touch + mouse). Dragging is restricted to a handle selector. Commits on SortableJS `onEnd` with `{ dragIndex, dropIndex, value }`. Keeps an optimistic `reorderedItems` buffer until the parent updates `items`. |
41
41
  | `useTableColumnsReorder` | Drag-and-drop column reordering with 50% horizontal threshold. Returns `getReorderHeadProps()` for easy binding. |
42
42
  | `useTableColumnsResize` | Weight-based resize: dragging adjusts the weights of the dragged column and its right neighbor without touching other columns. DOM-based px updates during drag for smooth 60fps; commits new weights to `columnState` on mouseup. No `ResizeObserver` scaling. |
43
43
  | `useTableSelectionV2` | Row selection: single, multiple (checkbox), and row-click modes. Emits `RowSelectEvent` / `RowSelectAllEvent`. |
@@ -160,7 +160,7 @@ register({
160
160
  ### Contributor notes
161
161
 
162
162
  - `useDataTableState`: Guard against save-during-restore loops with the `isRestoring` flag.
163
- - `useTableRowReorder`: `event.preventDefault()` in `dragover` MUST be called on every event or `drop` never fires.
163
+ - `useTableRowReorder`: powered by SortableJS with `forceFallback: true` so it works on touch. Dragging is limited to the `handle` selector. The optimistic `reorderedItems`/`pendingReorder` buffer makes Vue the source of DOM truth and hides SortableJS's raw DOM mutation until the parent updates `items`.
164
164
  - `useTableColumnsResize` applies DOM-level px changes during drag for 60fps performance, then commits final weights to `columnState` on mouseup. No `ResizeObserver` scaling is involved.
165
165
 
166
166
  <!-- internal:end -->
@@ -641,7 +641,7 @@ When `show-all-columns` is `false`, only `image` and `name` remain visible.
641
641
 
642
642
  ## Row Reorder
643
643
 
644
- Enable drag-and-drop row reordering with a drag handle column:
644
+ Enable drag-and-drop row reordering. A grip handle appears on the left of each row (desktop) or card (mobile) whenever `reorderable-rows` is enabled, and dragging is initiated from that handle. Reorder works on both desktop and touch devices (powered by SortableJS):
645
645
 
646
646
  ```vue
647
647
  <template>
@@ -678,7 +678,7 @@ function onReorder(event: { dragIndex: number; dropIndex: number; value: Product
678
678
  </script>
679
679
  ```
680
680
 
681
- > **Tip:** The drag handle column renders a grip icon. Without `row-reorder` on a VcColumn, the entire row is draggable (less precise, but works).
681
+ > **Tip:** Setting `:reorderable-rows="true"` is enough a grip handle is shown automatically and is the only drag affordance (so row clicks, mobile swipe actions, and long-press selection keep working). A dedicated `:row-reorder` VcColumn is optional and simply reserves an aligned column slot for the handle. On mobile, drag the handle to reorder cards.
682
682
 
683
683
  ---
684
684
 
File without changes
package/CHANGELOG.md DELETED
@@ -1,147 +0,0 @@
1
- # Changelog
2
-
3
- ## [2.0.6](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.5...v2.0.6) (2026-05-25)
4
-
5
- ### Features
6
-
7
- - docs-sync improvements, lint rules, and reference docs alignment (#229) ([a3ddbc2](https://github.com/VirtoCommerce/vc-shell/commit/a3ddbc29a90e55632df702f776e11bc8bc4aec67)), closes [#229](https://github.com/VirtoCommerce/vc-shell/issues/229)
8
-
9
- ## [2.0.5](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.4...v2.0.5) (2026-05-25)
10
-
11
- **Note:** Version bump only for package @vc-shell/vc-app-skill
12
-
13
- ## [2.0.4](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.3...v2.0.4) (2026-05-18)
14
-
15
- **Note:** Version bump only for package @vc-shell/vc-app-skill
16
-
17
- ## [2.0.3](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.2...v2.0.3) (2026-04-30)
18
-
19
- **Note:** Version bump only for package @vc-shell/vc-app-skill
20
-
21
- ## [2.0.2](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.1...v2.0.2) (2026-04-27)
22
-
23
- **Note:** Version bump only for package @vc-shell/vc-app-skill
24
-
25
- ## [2.0.1](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.0...v2.0.1) (2026-04-24)
26
-
27
- ### Features
28
-
29
- - 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)
30
-
31
- ### Bug Fixes
32
-
33
- - **hooks:** update npm version retrieval in vc-app-check-update.js ([c8f0e68](https://github.com/VirtoCommerce/vc-shell/commit/c8f0e68ded994bdfb8d01dcf96e90184b9d278cd))
34
-
35
- # [2.0.0](https://github.com/VirtoCommerce/vc-shell/compare/v1.2.3...v2.0.0) (2026-04-22)
36
-
37
- ### Features
38
-
39
- - **docs:** add documentation for usePopup and useResponsive composables ([342a50a](https://github.com/VirtoCommerce/vc-shell/commit/342a50ada1545637782e01f5452a60839aa90fd2))
40
- - **migrate:** add use-data-table-pagination-audit + AI migration prompt ([0c2e7b6](https://github.com/VirtoCommerce/vc-shell/commit/0c2e7b6efba387d9fafe04e5bdcd21ea20500259))
41
- - **migrate:** expand v2 migration tooling — icon/asset/audit prompts and blade-event cleanup ([f4788d4](https://github.com/VirtoCommerce/vc-shell/commit/f4788d4d9c588157ca5c11facfe558a69c254c2e)), closes [#41](https://github.com/VirtoCommerce/vc-shell/issues/41)
42
- - **vc-app-skill:** add /vc-app design command documentation ([3cc59b2](https://github.com/VirtoCommerce/vc-shell/commit/3cc59b29831da7f2f78900c7f61354165583813e))
43
- - **vc-app-skill:** add /vc-app design phases 1-6 (prompt → plan → execute → summary) ([b0bb44f](https://github.com/VirtoCommerce/vc-shell/commit/b0bb44f47e28fe9df147c5941a15e76b5ae3d2f8))
44
- - **vc-app-skill:** add /vc-app design to routing table and help ([8b2f53e](https://github.com/VirtoCommerce/vc-shell/commit/8b2f53e7d8643f8ab87f3fab82dc4dd8c8bd44da))
45
- - **vc-app-skill:** add /vc-app migrate command with full migration pipeline ([35a8b11](https://github.com/VirtoCommerce/vc-shell/commit/35a8b119457b14f6f4626caf133476222afd6663))
46
- - **vc-app-skill:** add /vc-app promote command with 5-phase flow ([a3b6da6](https://github.com/VirtoCommerce/vc-shell/commit/a3b6da658322a1e39ffb9808d0e80bf732f52ae8))
47
- - **vc-app-skill:** add blade-enhancer agent for surgical module modifications ([bc83681](https://github.com/VirtoCommerce/vc-shell/commit/bc83681c99accd3a25b48a5da1916810a6393aa0))
48
- - **vc-app-skill:** add design-specific error handling scenarios ([5381685](https://github.com/VirtoCommerce/vc-shell/commit/5381685b75a7eac82add64314cab8269518ba53f))
49
- - **vc-app-skill:** add enhance flow to /vc-app generate for existing modules ([2936bd6](https://github.com/VirtoCommerce/vc-shell/commit/2936bd60946d4c63207682908a8edbb8f137e448))
50
- - **vc-app-skill:** add entry-point and update commands ([ab9075f](https://github.com/VirtoCommerce/vc-shell/commit/ab9075f49b1bb2d93f56613758d038ef1ca09d09))
51
- - **vc-app-skill:** add existingModule context and append mode to generators ([3978c66](https://github.com/VirtoCommerce/vc-shell/commit/3978c66aff3b2526f51227c560371be425018ad2))
52
- - **vc-app-skill:** add knowledge-stats script for knowledge base audit ([7a568fc](https://github.com/VirtoCommerce/vc-shell/commit/7a568fc57335d51349a644721c904586efd49273))
53
- - **vc-app-skill:** add migration prompt knowledge base (nswag, widgets, form, blade-props, notifications) ([a0b0eb2](https://github.com/VirtoCommerce/vc-shell/commit/a0b0eb2a96521916a72bdbf787961c1c96f96f6d))
54
- - **vc-app-skill:** add migration-agent subagent prompt ([29ac155](https://github.com/VirtoCommerce/vc-shell/commit/29ac1550c8e3c9b71d5dbc2ade7ea86b4d40213f))
55
- - **vc-app-skill:** add module-analyzer agent for existing module introspection ([9eaff6b](https://github.com/VirtoCommerce/vc-shell/commit/9eaff6b3ca829ce41298c739f26ba6bdf93a2adb))
56
- - **vc-app-skill:** add multi-runtime installer ([00e656e](https://github.com/VirtoCommerce/vc-shell/commit/00e656e65bef05ebe8e7cb29cbf2efe64b991f5a))
57
- - **vc-app-skill:** add platform URL prompt and update design plan structure ([7855830](https://github.com/VirtoCommerce/vc-shell/commit/78558303247107ff55d5b0aeb3f5fdf50cad16f8))
58
- - **vc-app-skill:** add promote-agent subagent for mock-to-API transformation ([27484e3](https://github.com/VirtoCommerce/vc-shell/commit/27484e39c72e7b738e8db5846063e6388f7e06db))
59
- - **vc-app-skill:** add SessionStart update check hook ([72c5bcf](https://github.com/VirtoCommerce/vc-shell/commit/72c5bcf33d4f8ae826e7ed76d9228c42cfa846d4))
60
- - **vc-app-skill:** add uninstaller ([6a0507c](https://github.com/VirtoCommerce/vc-shell/commit/6a0507c9a5c755c0b4487c5625898c5c50c38986))
61
- - **vc-app-skill:** create package scaffold with README ([a2bbced](https://github.com/VirtoCommerce/vc-shell/commit/a2bbced57b2fbb62bb56ca38c679b0c42e7aee88))
62
- - **vc-app-skill:** enrich knowledge base with When to Use sections, patterns, and sync-docs script ([c82ed63](https://github.com/VirtoCommerce/vc-shell/commit/c82ed639d748bdc8fd2d9c39435ee37baedd0563))
63
- - **vc-app-skill:** migrate skill content from create-vc-app to standalone package ([1ac8945](https://github.com/VirtoCommerce/vc-shell/commit/1ac894510ca32cad22efb226117127f76085f85a))
64
- - **vc-app-skill:** update scaffold to skip --module-name for standalone ([c82066e](https://github.com/VirtoCommerce/vc-shell/commit/c82066e3c910676ada13f0cd8f1d7db72d81982a))
65
- - **vc-app:** add sidebar search bar for menu filtering ([72f17fc](https://github.com/VirtoCommerce/vc-shell/commit/72f17fc5b0e77e4e87457c5a29262345da50317d))
66
-
67
- ### Bug Fixes
68
-
69
- - **datatable:** normalise date-range filter values to YYYY-MM-DD ([d89864a](https://github.com/VirtoCommerce/vc-shell/commit/d89864aa635e7479137fb0ad501197adf335f99e))
70
- - **migrate:** datatable prompt forbids 'removed filters for green build' shortcut ([af5ae8e](https://github.com/VirtoCommerce/vc-shell/commit/af5ae8e4259b435729e6cea9e4c61d74b41d3952))
71
- - **migrate:** use-data-table-pagination prompt — per-file scope skip ([8949f01](https://github.com/VirtoCommerce/vc-shell/commit/8949f01da97b69c15fc6e3a2fca951608731979c))
72
- - **release:** update changelog and release-it configuration ([767c312](https://github.com/VirtoCommerce/vc-shell/commit/767c3123773a02a4badc3bcf89661e535d5f26c8))
73
- - **vc-app-skill:** add menuItem to standalone details blades (isWorkspace entry point) ([6be54ad](https://github.com/VirtoCommerce/vc-shell/commit/6be54ad38613ea0de865ab1348236417da8a572d))
74
- - **vc-app-skill:** add VC_SHELL_MIGRATE_CLI env override for local testing ([010d2f2](https://github.com/VirtoCommerce/vc-shell/commit/010d2f2ecb29d31d9134406bcd18e46e5883208b))
75
- - **vc-app-skill:** auto-run yarn install after project scaffold ([cb9ada3](https://github.com/VirtoCommerce/vc-shell/commit/cb9ada34579f89121de5648eee65b346bcd4d924))
76
- - **vc-app-skill:** clarify app.use ordering — after framework, before router ([97439e4](https://github.com/VirtoCommerce/vc-shell/commit/97439e4cfedcae81a6fc26721b91d79187e95f1a))
77
- - **vc-app-skill:** correct mock template in details-blade-generator ([04b7d43](https://github.com/VirtoCommerce/vc-shell/commit/04b7d43dc12cb66ad786f91017e48c4c30e889e9))
78
- - **vc-app-skill:** register generated modules in main.ts (import + app.use) ([e88d75e](https://github.com/VirtoCommerce/vc-shell/commit/e88d75e86ed0d4f69d3261d3d04e3b68bfb3baa5))
79
- - **vc-app-skill:** resolve migrate CLI from project node_modules before npx fallback ([82a8156](https://github.com/VirtoCommerce/vc-shell/commit/82a81563f7fdb94a94a7b8a80796ecffc671a39c))
80
- - **vc-app-skill:** resolve migrate CLI locally in dev mode, fallback to npx ([89888a0](https://github.com/VirtoCommerce/vc-shell/commit/89888a06cc0b4a1519f46f070ee06ebb0d82fd09))
81
- - **vc-app-skill:** use @alpha dist-tag for create-vc-app commands ([09b4a27](https://github.com/VirtoCommerce/vc-shell/commit/09b4a273116d9b04c00ff1c6ad19af2d869bd394))
82
- - **vc-app-skill:** use @alpha dist-tag for update command and version check hook ([cceaab9](https://github.com/VirtoCommerce/vc-shell/commit/cceaab926a3a51160d8994891bc88445bfb9a9aa))
83
- - **vc-app-skill:** validate runtime arg and fix command listing ([f7169af](https://github.com/VirtoCommerce/vc-shell/commit/f7169afa56092be2e223a61c0edd5b73cd653d77))
84
-
85
- ### Code Refactoring
86
-
87
- - clean up code formatting and improve readability across multiple files ([9f710b5](https://github.com/VirtoCommerce/vc-shell/commit/9f710b5ceba270475709735c139e061c66c96b18))
88
- - **core:** make user composables a true singleton ([72391b6](https://github.com/VirtoCommerce/vc-shell/commit/72391b610e0bd520b0814641eedfc5163058236a))
89
- - rename App Switcher to App Hub ([1646b22](https://github.com/VirtoCommerce/vc-shell/commit/1646b2276791f2448e0e69e77ca25c96857a3975))
90
- - rename vc-table to vc-data-table, extract usePopup to core composables ([15cc93d](https://github.com/VirtoCommerce/vc-shell/commit/15cc93dc871d6d23344aa8d0fff7074528fea001))
91
-
92
- ### Documentation
93
-
94
- - add comprehensive documentation for useDataTablePagination composable ([a783cd9](https://github.com/VirtoCommerce/vc-shell/commit/a783cd9a7a5837267d4c11cc3279f2b70d0e6734))
95
- - document peer-versions.json and --update-deps peer alignment ([4de1dfd](https://github.com/VirtoCommerce/vc-shell/commit/4de1dfd032194a6e2797055abfcd94f61f117cf6))
96
- - update references from IBladeInstance to BladeDescriptor ([ef380d8](https://github.com/VirtoCommerce/vc-shell/commit/ef380d80df9247fdc70e42aed411a48239a294fa))
97
- - **vc-app-skill:** document /vc-app migrate command and migration-agent ([5ded9f1](https://github.com/VirtoCommerce/vc-shell/commit/5ded9f1a98028a07327d06254e88ac8635c4fd66))
98
- - **vc-app-skill:** update README and simplify create command ([1edd79d](https://github.com/VirtoCommerce/vc-shell/commit/1edd79d746b57cc77fc9c5dd9278c81f6c575bbe))
99
- - **vc-app-skill:** update README to remove release section and ensure proper formatting ([0e24e48](https://github.com/VirtoCommerce/vc-shell/commit/0e24e481ec03583b41b29ce47f8dc623020a1079))
100
- - **vc-app-skill:** use @alpha tag in install commands until stable release ([d82529c](https://github.com/VirtoCommerce/vc-shell/commit/d82529c32386770945b6aff35b1d0bf01a414b79))
101
-
102
- ### Styles
103
-
104
- - **lint:** one-time cleanup of pre-existing violations and tech debt ([a7113c5](https://github.com/VirtoCommerce/vc-shell/commit/a7113c5d25b5b4dc9da20f6bc40c54b57fe46422))
105
-
106
- ### Chores
107
-
108
- - auto-sync vc-app-skill VERSION and docs during release ([140897d](https://github.com/VirtoCommerce/vc-shell/commit/140897d2f31b98eef88f17309baa76fbdaae821f))
109
- - **docs:** generalize private references in migration tooling and skill ([4719e51](https://github.com/VirtoCommerce/vc-shell/commit/4719e51586ff4dae9693c33d195072408111f3c0))
110
- - **release-it:** add before:stage hook to stage runtime files before release ([a8f0b8c](https://github.com/VirtoCommerce/vc-shell/commit/a8f0b8cd7c4cb6c0cd9a789003d75f81ac2fb2d0))
111
- - **scripts:** normalize yarn scripts per industry standards ([1cdd0cb](https://github.com/VirtoCommerce/vc-shell/commit/1cdd0cb517d2436ef2a509c6b6c358f6a48630d1))
112
- - track vc-app-skill knowledge docs in repository ([9189f08](https://github.com/VirtoCommerce/vc-shell/commit/9189f0842a01e7c46d06c28243236ccdfb8bf04f))
113
- - update package.json to include publishConfig for npm registry ([cf5f3ed](https://github.com/VirtoCommerce/vc-shell/commit/cf5f3edd1a048a9bb16e5ac11d4aa5f717a717c5))
114
- - **vc-app-skill:** add to release pipeline and bump to 2.0.0-alpha.16 ([f703552](https://github.com/VirtoCommerce/vc-shell/commit/f7035529020bb330a5f50840c50ad0843828ebf1))
115
-
116
- ### Other Changes
117
-
118
- - Merge remote-tracking branch 'origin/main' ([bb2c2da](https://github.com/VirtoCommerce/vc-shell/commit/bb2c2da88fd9ac8d58eb0a84451d80bfba3f2547))
119
-
120
- ### release
121
-
122
- - v2.0.0-alpha.17 ([408e4af](https://github.com/VirtoCommerce/vc-shell/commit/408e4af487f37b8aff790398d5a992820c8f05a2))
123
- - v2.0.0-alpha.18 ([2466e35](https://github.com/VirtoCommerce/vc-shell/commit/2466e359313c9d78893d2473474ec5ce46ad49ca))
124
- - v2.0.0-alpha.19 ([9d5a075](https://github.com/VirtoCommerce/vc-shell/commit/9d5a075e0a722c6c7371706f582b59bbf570ef37))
125
- - v2.0.0-alpha.20 ([98bd4a6](https://github.com/VirtoCommerce/vc-shell/commit/98bd4a62ffa933bbfdae16ef7e46777084f15190))
126
- - v2.0.0-alpha.21 ([bbd0d70](https://github.com/VirtoCommerce/vc-shell/commit/bbd0d70464ff5c0dafbc67ed3bf8ea36f65b0a7a))
127
- - v2.0.0-alpha.22 ([ec455eb](https://github.com/VirtoCommerce/vc-shell/commit/ec455eba6bb91ae5fdbea64817ce1650116b9d50))
128
- - v2.0.0-alpha.23 ([65b2144](https://github.com/VirtoCommerce/vc-shell/commit/65b214434ef1630069aecd6f2eaf916531bf37ef))
129
- - v2.0.0-alpha.24 ([05ff510](https://github.com/VirtoCommerce/vc-shell/commit/05ff5107b81e0280447872ea52771fc47cb3cd67))
130
- - v2.0.0-alpha.25 ([19d09b9](https://github.com/VirtoCommerce/vc-shell/commit/19d09b998861836b288aba36a6d18d7b26cff0b7))
131
- - v2.0.0-alpha.26 ([ee6818c](https://github.com/VirtoCommerce/vc-shell/commit/ee6818ceb60b24d26b7011f2584c8d23f6b4fd3e))
132
- - v2.0.0-alpha.27 ([f43652d](https://github.com/VirtoCommerce/vc-shell/commit/f43652d1aae8af36d5d2f00312492ea9de57f2f1))
133
- - v2.0.0-alpha.28 ([7954dfd](https://github.com/VirtoCommerce/vc-shell/commit/7954dfddf1126d00038b8f66a6fe3efc5c19cfe8))
134
- - v2.0.0-alpha.29 ([ffa72cf](https://github.com/VirtoCommerce/vc-shell/commit/ffa72cf458eed77c78955c88d211a82ead152b0f))
135
- - v2.0.0-alpha.30 ([2db7f17](https://github.com/VirtoCommerce/vc-shell/commit/2db7f17d74afe97e3c6dfef2de436a797f0c32f4))
136
- - v2.0.0-alpha.31 ([8d92fba](https://github.com/VirtoCommerce/vc-shell/commit/8d92fbad5954c71164e7815193b3496e569a5703))
137
- - v2.0.0-alpha.32 ([1ed5533](https://github.com/VirtoCommerce/vc-shell/commit/1ed5533a6a20081e655f2e628bf824de40472f5d))
138
- - v2.0.0-alpha.33 ([49cad36](https://github.com/VirtoCommerce/vc-shell/commit/49cad36a454534136b52576e6a0d97dfe48ae895))
139
- - v2.0.0-alpha.34 ([78bed5a](https://github.com/VirtoCommerce/vc-shell/commit/78bed5af3aeb7ace2eb9f58ddde3235fced47b37))
140
- - v2.0.0-alpha.35 ([05d6f25](https://github.com/VirtoCommerce/vc-shell/commit/05d6f2562c939dd4a2e7e4e7a3d80948beccbef1))
141
-
142
- ### BREAKING CHANGES
143
-
144
- - **scripts:** for external consumers: old script names
145
- (storybook-serve, build-framework, check-locales etc) are removed.
146
- Legacy aliases are deliberately not provided — they would perpetuate
147
- the non-standard naming this commit eliminates.
@@ -1,34 +0,0 @@
1
- ---
2
- title: usePlatformLocaleSync
3
- category: composables
4
- group: user
5
- ---
6
-
7
- # usePlatformLocaleSync
8
-
9
- One-way reactive bridge from the VirtoCommerce platform's locale storage key (`NG_TRANSLATE_LANG_KEY`, set by AngularJS + angular-translate) to the shell's language service.
10
-
11
- Call this composable only when the shell runs embedded inside the platform — `useShellBootstrap` invokes it automatically when `options.isEmbedded === true`. In standalone mode the shell owns its own locale via `VC_LANGUAGE_SETTINGS`, and this composable should not be used.
12
-
13
- ## When to Use
14
-
15
- - Never call directly from feature code. This is a framework-internal sync primitive.
16
- - It is invoked once per `VcApp` mount from `useShellBootstrap`.
17
-
18
- ## Behaviour
19
-
20
- - Reads `localStorage["NG_TRANSLATE_LANG_KEY"]` via VueUse's `useLocalStorage`, which subscribes to `storage` events for cross-tab reactivity.
21
- - On setup, if the value is non-empty, calls `LanguageService.setLocale(value)`. `setLocale` normalises the value (e.g. `en-US` → `en-us`), falls back to `en` for unsupported locales, updates `vue-i18n`, reconfigures `vee-validate`, and persists to `VC_LANGUAGE_SETTINGS`.
22
- - On subsequent changes of the platform key, re-applies the value.
23
- - Skips empty strings (platform clearing the key does not blank the shell locale).
24
- - Skips values equal to `currentLocale` to avoid redundant re-configuration.
25
-
26
- ## How It Works
27
-
28
- `useLocalStorage("NG_TRANSLATE_LANG_KEY", "")` returns a `Ref<string>` that VueUse keeps in sync with `localStorage` and the DOM `storage` event (which fires in tabs other than the writer). The composable applies the current ref value once synchronously and then registers a `watch` on it; any cross-tab mutation flows through the ref into `setLocale`.
29
-
30
- The watcher is bound to the active effect scope (typically `VcApp`'s setup). When `VcApp` unmounts, the watcher stops; `useLocalStorage` cleans up its own `storage` listener.
31
-
32
- ## Relationship to `VC_LANGUAGE_SETTINGS`
33
-
34
- The sync is strictly one-directional. `setLocale` writes to `VC_LANGUAGE_SETTINGS` as a side effect, but this composable never writes to `NG_TRANSLATE_LANG_KEY`. In embedded mode the in-shell `LanguageSelector` is unreachable (it lives inside `UserDropdownButton`, which is hidden when `isEmbedded` is `true`), so there is no competing writer from the shell side.
@@ -1,184 +0,0 @@
1
- ---
2
- title: useBladeNotifications
3
- category: composables
4
- group: notifications
5
- ---
6
-
7
- # useBladeNotifications
8
-
9
- Subscribes a blade to one or more push-notification types from the platform's SignalR stream. The composable returns a reactive list of matching unread messages, an unread count, and a `markAsRead` action. The subscription is bound to the current effect scope, so it disappears the moment the blade closes — no manual unsubscribe.
10
-
11
- This is the **Level 2** entry point in the notification system. Level 1 — `defineAppModule({ notifications })` — registers types globally with their toast configuration and is the always-on path. Level 2 layers blade-specific behavior on top of that: refresh a list, update a progress UI, mark a job complete.
12
-
13
- ## When to use
14
-
15
- - A list blade needs to refresh when an entity is created, updated, or deleted elsewhere.
16
- - A long-running operation has a dedicated blade and the blade should update as `processedCount` / `errorCount` flow in.
17
- - A blade wants to surface an inline "N new" badge for messages of a specific type.
18
- - When NOT to use: app-wide toasts already come from the Level 1 module config — the blade does not need to subscribe just to show a toast. Reach for the blade subscription only when you also need to _react_ to the event in code.
19
-
20
- ## Quick Start
21
-
22
- ```ts
23
- import { useBladeNotifications } from "@vc-shell/framework";
24
-
25
- useBladeNotifications({
26
- types: ["OfferDeletedDomainEvent"],
27
- onMessage: () => reload(),
28
- });
29
- ```
30
-
31
- That is the full recipe for "refresh this list when an offer is deleted somewhere in the app." The handler runs once per matching message; the framework cleans up the subscription when the blade unmounts.
32
-
33
- ## API
34
-
35
- ### Parameters
36
-
37
- ```typescript
38
- interface BladeNotificationOptions<T extends PushNotification = PushNotification> {
39
- types: string[];
40
- filter?: (msg: T) => boolean;
41
- onMessage?: (msg: T) => void;
42
- }
43
- ```
44
-
45
- | Field | Type | Required | Description |
46
- | ----------- | --------------------- | -------- | ------------------------------------------------------------------------------------------------------- |
47
- | `types` | `string[]` | Yes | Notification types to subscribe to. Must match the `notifyType` field on incoming messages. |
48
- | `filter` | `(msg: T) => boolean` | No | Narrow the subscription further (for example, only events for the entity this blade is editing). |
49
- | `onMessage` | `(msg: T) => void` | No | Callback fired once per matching message. Use it to refresh data, mark progress, or update local state. |
50
-
51
- ### Returns
52
-
53
- ```typescript
54
- interface BladeNotificationReturn<T extends PushNotification = PushNotification> {
55
- messages: ComputedRef<T[]>;
56
- unreadCount: ComputedRef<number>;
57
- markAsRead: (msg: T) => void;
58
- }
59
- ```
60
-
61
- | Property | Type | Description |
62
- | ------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------- |
63
- | `messages` | `ComputedRef<T[]>` | Realtime messages matching `types` and `filter` that are still unread. Updates reactively as new messages arrive. |
64
- | `unreadCount` | `ComputedRef<number>` | `messages.value.length`. Bind to a badge. |
65
- | `markAsRead` | `(msg: T) => void` | Mark a specific message as read. Removes it from `messages` (and reduces the global unread badge in the bell dropdown). |
66
-
67
- ## Typed payloads
68
-
69
- Notification payloads often extend `PushNotification` with domain fields. Pass the type parameter so `onMessage` and `messages` are typed:
70
-
71
- ```ts
72
- import type { PushNotification } from "@vc-shell/framework";
73
-
74
- interface ImportPushNotification extends PushNotification {
75
- jobId: string;
76
- profileId: string;
77
- profileName?: string;
78
- processedCount: number;
79
- errorCount: number;
80
- finished: boolean;
81
- }
82
-
83
- const { messages, markAsRead } = useBladeNotifications<ImportPushNotification>({
84
- types: ["ImportPushNotification"],
85
- onMessage: (message) => {
86
- if (message.finished) {
87
- reload();
88
- markAsRead(message);
89
- }
90
- },
91
- });
92
- ```
93
-
94
- ## Common patterns
95
-
96
- ### Refresh a list on any matching event
97
-
98
- ```ts
99
- useBladeNotifications({
100
- types: ["OfferDeletedDomainEvent"],
101
- onMessage: () => reload(),
102
- });
103
- ```
104
-
105
- Drop-in for a list blade that needs to stay in sync with deletions happening anywhere in the app.
106
-
107
- ### Filter to the entity this blade owns
108
-
109
- ```ts
110
- useBladeNotifications<ImportPushNotification>({
111
- types: ["ImportPushNotification"],
112
- onMessage: (message) => {
113
- if (message.profileId !== param.value) return; // not our job
114
- if (!message.finished) updateProgress(message);
115
- else finalizeImport(message);
116
- },
117
- });
118
- ```
119
-
120
- Two open import blades will both receive the stream; each one filters by its own `profileId` so they do not step on each other.
121
-
122
- ### Drive a manual progress toast
123
-
124
- When the platform sends progress updates for a long-running job, you may want to render one persistent toast that you update as messages arrive — instead of letting Level 1 spawn a new toast per event.
125
-
126
- Set the Level 1 type to `silent` and drive the toast yourself:
127
-
128
- ```ts title="src/modules/import/index.ts"
129
- defineAppModule({
130
- notifications: {
131
- ImportPushNotification: { toast: { mode: "silent" } },
132
- },
133
- // ...
134
- });
135
- ```
136
-
137
- ```ts title="pages/import-process.vue"
138
- import { useBladeNotifications, notification } from "@vc-shell/framework";
139
-
140
- let toastId: string | undefined;
141
-
142
- useBladeNotifications<ImportPushNotification>({
143
- types: ["ImportPushNotification"],
144
- onMessage: (message) => {
145
- const content = message.profileName ? `${message.profileName}: ${message.title}` : message.title;
146
-
147
- if (!toastId) {
148
- toastId = notification(content, { timeout: false });
149
- } else if (!message.finished) {
150
- notification.update(toastId, { content });
151
- } else {
152
- notification.update(toastId, {
153
- content,
154
- timeout: 5000,
155
- type: message.errorCount ? "error" : "success",
156
- onClose: () => (toastId = undefined),
157
- });
158
- }
159
- },
160
- });
161
- ```
162
-
163
- The `notification()` helper returns the toast id; `notification.update` mutates it in place. The bell-dropdown history still grows — `silent` only suppresses the auto-toast.
164
-
165
- ## Lifecycle
166
-
167
- `useBladeNotifications` calls `useNotificationStore().subscribe(...)` and registers `onScopeDispose(unsub)` against the current effect scope. Inside a Vue `setup()` (component or `<script setup>`) the scope is the component's; the subscription dies with the component.
168
-
169
- If you call the composable from a manually managed `effectScope()`, the cleanup runs when that scope is stopped. Calling it outside any scope is a bug — the subscription would never be released.
170
-
171
- ## Tips
172
-
173
- - **Listen, do not declare.** `useBladeNotifications` does not register the notification type with the framework. Types must already be declared by some module via `defineAppModule({ notifications })`, otherwise nothing reaches `onMessage`.
174
- - **`messages` shows only unread.** `markAsRead(msg)` removes a message from `messages` (and from the global unread count). The notification stays in history.
175
- - **One subscription per call.** Calling `useBladeNotifications` multiple times in the same blade creates independent subscriptions. Combine handlers if you only need one.
176
- - **Type strings are case-sensitive.** The string in `types` must exactly equal the `notifyType` field on incoming messages.
177
-
178
- ## Related
179
-
180
- - [useNotificationStore](./useNotificationStore.md) — direct store access for app-shell features (dropdown, badge).
181
- - [useNotificationContext](./useNotificationContext.md) — read the current notification inside a custom template.
182
- - [useBroadcastFilter](./useBroadcastFilter.md) — control which `SendSystemEvents` broadcasts reach the store.
183
- - [Notifications concept page.](../../concepts/notifications.md)
184
- - [Notifications plugin reference.](../../plugins/notifications.md)
@@ -1,117 +0,0 @@
1
- ---
2
- title: useBroadcastFilter
3
- category: composables
4
- group: notifications
5
- ---
6
-
7
- # useBroadcastFilter
8
-
9
- Controls which **broadcast** push notifications reach the store. The platform SignalR hub delivers messages through two channels: `Send` (targeted to a specific user) and `SendSystemEvents` (broadcast to everyone connected). Broadcasts run through the filter installed here; targeted messages are always accepted.
10
-
11
- This is the standard mechanism for scoping a multi-tenant app — show a seller only the broadcasts that mention them, hide events from other tenants. Without a filter every broadcast lands in every user's history.
12
-
13
- ## When to use
14
-
15
- - Multi-tenant apps where the same broadcast topic carries events for different tenants (sellers, organizations, departments) and each user should only see their slice.
16
- - Apps that want to drop noisy `IndexProgressPushNotification` or similar maintenance events for non-admin roles.
17
- - When NOT to use: filtering targeted notifications. The platform already delivers `Send` messages only to the addressed user; `useBroadcastFilter` does not see them.
18
-
19
- ## Quick Start
20
-
21
- ```ts
22
- import { useBroadcastFilter, useUser } from "@vc-shell/framework";
23
- import { onMounted } from "vue";
24
-
25
- const { user } = useUser();
26
- const { setBroadcastFilter } = useBroadcastFilter();
27
-
28
- onMounted(() => {
29
- setBroadcastFilter((msg) => msg.creator === user.value?.userName);
30
- });
31
- ```
32
-
33
- Install the filter once at app bootstrap (or whenever the active user changes). Every incoming broadcast is run through it; messages that return `false` are dropped before they touch history, toasts, or subscribers.
34
-
35
- ## API
36
-
37
- ### Returns
38
-
39
- ```typescript
40
- interface UseBroadcastFilterReturn {
41
- setBroadcastFilter(fn: (msg: PushNotification) => boolean): void;
42
- clearBroadcastFilter(): void;
43
- }
44
- ```
45
-
46
- | Method | Type | Description |
47
- | ---------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------- |
48
- | `setBroadcastFilter` | `((msg: PushNotification) => boolean) => void` | Install the filter. Replaces any previous filter — there is at most one active at a time. |
49
- | `clearBroadcastFilter` | `() => void` | Remove the filter. All subsequent broadcasts are accepted. |
50
-
51
- The filter returns `true` to **accept** a message, `false` to **drop** it.
52
-
53
- ## Common patterns
54
-
55
- ### Scope by current user
56
-
57
- ```ts
58
- onMounted(() => {
59
- setBroadcastFilter((msg) => msg.creator === user.value?.userName);
60
- });
61
- ```
62
-
63
- `creator` is the user that originated the event on the platform side. This is the canonical "show me my own broadcasts" filter in multi-tenant back-office apps.
64
-
65
- ### Scope by tenant id
66
-
67
- ```ts
68
- onMounted(() => {
69
- setBroadcastFilter((msg) => (msg as TenantPush).sellerId === currentSellerId.value);
70
- });
71
- ```
72
-
73
- When your broadcast payloads carry a tenant id, gate on it instead of `creator`. The cast clarifies typing without expanding `PushNotification` for every caller.
74
-
75
- ### Re-install on user switch
76
-
77
- ```ts
78
- import { watch } from "vue";
79
-
80
- watch(
81
- () => user.value?.userName,
82
- (name) => {
83
- if (!name) clearBroadcastFilter();
84
- else setBroadcastFilter((msg) => msg.creator === name);
85
- },
86
- { immediate: true },
87
- );
88
- ```
89
-
90
- If the app supports user switching without a full reload (impersonation, multi-account), re-install the filter on every change. There is only one slot — installing again replaces the previous filter.
91
-
92
- ### Drop a noisy type entirely
93
-
94
- ```ts
95
- setBroadcastFilter((msg) => msg.notifyType !== "IndexProgressPushNotification");
96
- ```
97
-
98
- Broadcast-only suppression. To suppress targeted messages too, set `toast: false` or `toast: { mode: "silent" }` on the type's `defineAppModule({ notifications })` config — that controls the toast surface; the history still records the event.
99
-
100
- ## Behavior
101
-
102
- - The filter applies only to messages ingested with the `broadcast: true` flag (the SignalR `SendSystemEvents` channel).
103
- - Targeted messages (`Send`) bypass the filter entirely.
104
- - Installing a filter mid-session does not retroactively prune `history` or `realtime`. Past broadcasts stay; only future ones are filtered.
105
- - The filter is a single function. To compose multiple predicates, `&&` them inside one callback.
106
-
107
- ## Tips
108
-
109
- - **Install once, early.** Setting the filter in `App.vue` `onMounted` (after authentication) is the canonical placement, so messages arriving before the first blade mounts are already scoped.
110
- - **Filter exceptions go straight to the console.** If your predicate throws, the message is dropped. Wrap the logic if you are reading off potentially missing fields.
111
- - **Do not query the store from inside the filter.** The store is `useBroadcastFilter`'s parent — calling back into it during ingestion causes re-entrancy.
112
-
113
- ## Related
114
-
115
- - [useNotificationStore](./useNotificationStore.md) — exposes the same set/clear methods plus the rest of the store API.
116
- - [useBladeNotifications](./useBladeNotifications.md) — blade-scoped subscription that sees broadcasts after filtering.
117
- - [Notifications concept page — broadcasts.](../../concepts/notifications.md#broadcast-vs-targeted)
@@ -1,150 +0,0 @@
1
- ---
2
- title: useNotificationContext
3
- category: composables
4
- group: notifications
5
- ---
6
-
7
- # useNotificationContext
8
-
9
- Reads the current `PushNotification` payload inside a custom notification template. The framework provides the message via Vue's `inject()` from the dropdown or toast surface that hosts the template — the composable returns a reactive `ComputedRef` over it.
10
-
11
- This is the one piece you write when a notification type registered with `defineAppModule({ notifications: { Type: { template } } })` needs a richer rendering than the default `NotificationTemplate` chrome — for example, formatting a domain-specific status, deriving a colored accent, or wiring a click handler that opens the relevant blade.
12
-
13
- ## When to use
14
-
15
- - Implementing the `template` component for a notification type registered through `defineAppModule({ notifications })`.
16
- - Reading typed payload fields (status, entity name, job id) to drive the template's layout, color, or actions.
17
- - When NOT to use: anywhere outside a notification template. The composable throws if the inject context is not present.
18
-
19
- ## Quick Start
20
-
21
- ```vue
22
- <script lang="ts" setup>
23
- import { PushNotification, NotificationTemplate, useNotificationContext } from "@vc-shell/framework";
24
- import { computed } from "vue";
25
-
26
- interface IOrderPushNotification extends PushNotification {
27
- orderId: string;
28
- total: number;
29
- }
30
-
31
- const notificationRef = useNotificationContext<IOrderPushNotification>();
32
- const notification = computed(() => notificationRef.value);
33
- </script>
34
-
35
- <template>
36
- <NotificationTemplate
37
- :title="notification.title ?? ''"
38
- :notification="notification"
39
- >
40
- <p>Order {{ notification.orderId }} — ${{ notification.total }}</p>
41
- </NotificationTemplate>
42
- </template>
43
- ```
44
-
45
- ## API
46
-
47
- ### Parameters
48
-
49
- None. The composable is always called without arguments. Generic type parameter narrows the payload shape:
50
-
51
- ```typescript
52
- function useNotificationContext<T extends PushNotification = PushNotification>(): ComputedRef<T>;
53
- ```
54
-
55
- ### Returns
56
-
57
- `ComputedRef<T>` — reactive reference to the current `PushNotification` (or your extended subtype via the generic). Update reactively if the same template instance is reused for a refreshed payload (for example, when a progress message is updated through `notification.update`).
58
-
59
- ## Common patterns
60
-
61
- ### Compute display strings from payload fields
62
-
63
- ```vue
64
- <script lang="ts" setup>
65
- import { PushNotification, useNotificationContext, NotificationTemplate } from "@vc-shell/framework";
66
- import { computed } from "vue";
67
- import { useI18n } from "vue-i18n";
68
-
69
- interface IProductPush extends PushNotification {
70
- productName?: string;
71
- newStatus?: string;
72
- }
73
-
74
- const ctx = useNotificationContext<IProductPush>();
75
- const notification = computed(() => ctx.value);
76
- const { t } = useI18n({ useScope: "global" });
77
-
78
- const title = computed(() => (notification.value.productName ? `${t("PRODUCTS.PUSH.PRODUCT")} "${notification.value.productName}" ${t("PRODUCTS.PUSH.UPDATE")}` : (notification.value.title ?? "")));
79
- </script>
80
- ```
81
-
82
- ### Open a blade on click
83
-
84
- ```vue
85
- <script lang="ts" setup>
86
- import { useBlade, useNotificationContext, NotificationTemplate } from "@vc-shell/framework";
87
- import { computed } from "vue";
88
-
89
- const { openBlade } = useBlade();
90
- const ctx = useNotificationContext<IOrderPush>();
91
- const notification = computed(() => ctx.value);
92
-
93
- function onClick() {
94
- if (!notification.value.orderId) return;
95
- openBlade({ name: "OrderDetails", param: notification.value.orderId });
96
- }
97
- </script>
98
-
99
- <template>
100
- <NotificationTemplate
101
- :title="notification.title ?? ''"
102
- :notification="notification"
103
- @click="onClick"
104
- >
105
- <p>{{ notification.description }}</p>
106
- </NotificationTemplate>
107
- </template>
108
- ```
109
-
110
- `NotificationTemplate` re-emits the click event so the host (dropdown row, toast) can close itself before your handler runs.
111
-
112
- ### Color the template by status
113
-
114
- ```ts
115
- const notificationStyle = computed(() => {
116
- switch (notification.value.newStatus) {
117
- case "Approved":
118
- return { color: "var(--success-400)", icon: "lucide-check-circle" };
119
- case "RequestChanges":
120
- return { color: "var(--danger-400)", icon: "lucide-alert-circle" };
121
- case "WaitForApproval":
122
- return { color: "var(--warning-600)", icon: "lucide-clock" };
123
- default:
124
- return { color: "var(--primary-400)", icon: "lucide-bell" };
125
- }
126
- });
127
- ```
128
-
129
- `NotificationTemplate` accepts `:color` and `:icon` props that line up with these computeds — the dropdown row and the toast use the same template, so the styling stays consistent across surfaces.
130
-
131
- ## Where the template runs
132
-
133
- Notification templates render in two places:
134
-
135
- - **In the bell dropdown**, as one row in the history list.
136
- - **As a toast**, when the type's `toast.mode` is `"auto"` or `"progress"` (set the mode to `"silent"` to render only in the dropdown).
137
-
138
- The template component must be **the same** in both — register it once on `defineAppModule({ notifications })` and the framework reuses it everywhere.
139
-
140
- ## Tips
141
-
142
- - **Always type the generic.** `useNotificationContext<MyPushType>()` enables autocompletion on payload fields. Without it everything degrades to `PushNotification`.
143
- - **`computed(() => ctx.value)` is idiomatic** in the example apps because consumers want a regular `Ref` shape to pass into child components and template bindings. Direct access via `ctx.value` is fine too.
144
- - **Do not subscribe inside a template.** The template renders one message; if you need to react to other notifications, do that in a blade with `useBladeNotifications`.
145
-
146
- ## Related
147
-
148
- - [useBladeNotifications](./useBladeNotifications.md) — subscribe to types inside a blade.
149
- - [useNotificationStore](./useNotificationStore.md) — access the underlying store.
150
- - [Notifications concept page.](../../concepts/notifications.md)
@@ -1,113 +0,0 @@
1
- ---
2
- title: useNotificationStore
3
- category: composables
4
- group: notifications
5
- ---
6
-
7
- # useNotificationStore
8
-
9
- !!! warning "Advanced — most apps do not need this"
10
- Reach for [useBladeNotifications](./useBladeNotifications.md), [useBroadcastFilter](./useBroadcastFilter.md), and the `notifications` option on `defineAppModule` first. The bell dropdown, unread badge, and toast pipeline are already wired by the shell. This composable is an **escape hatch** for the small set of cases where those facades do not fit.
11
-
12
- Returns the singleton store that backs the framework's notification system. Direct access exposes the full reactive state plus low-level actions: subscribing, ingesting synthetic messages, controlling the broadcast filter, paging history.
13
-
14
- ## When to use
15
-
16
- There are only two cases where this composable belongs in app code:
17
-
18
- - **A test or scripted harness** that needs to push synthetic messages through the same pipeline SignalR uses, via `ingest`. Real-time production code never calls `ingest` directly.
19
- - **A custom shell** that replaces the framework's bell dropdown entirely — typically a module-federation host with its own chrome — and therefore needs to bind to `history`, `unreadCount`, and `markAllAsRead` outside the default surface. Apps that use the standard `VcApp` shell do not need this; the dropdown is already there.
20
-
21
- For everything else — reacting in a blade, gating broadcasts, registering types, displaying a toast — use the facades. Direct store mutations can bypass invariants the facades enforce (broadcast filter, scope-aware cleanup, type validation).
22
-
23
- ## Quick Start
24
-
25
- ```ts
26
- import { useNotificationStore } from "@vc-shell/framework";
27
-
28
- const store = useNotificationStore();
29
-
30
- await store.loadHistory(50);
31
- ```
32
-
33
- ## API
34
-
35
- The store exposes the full reactive state plus actions. The shape is summarized below; the underlying types live in `core/notifications/store.ts` and `core/notifications/types.ts`.
36
-
37
- | Member | Type | Description |
38
- | ------------------------ | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
39
- | `registry` | `Map<string, NotificationTypeConfig>` | Notification types registered through `defineAppModule({ notifications })`. |
40
- | `history` | `Ref<PushNotification[]>` | Full history (server-loaded plus ingested), newest first. |
41
- | `realtime` | `Ref<PushNotification[]>` | Session-only realtime queue from the SignalR hub. Drives `messages` in `useBladeNotifications`. |
42
- | `unreadCount` | `ComputedRef<number>` | Count of unread items in `history`. Drives the bell badge. |
43
- | `hasUnread` | `ComputedRef<boolean>` | Convenience boolean over `unreadCount`. |
44
- | `registerType(t, cfg)` | `(string, NotificationTypeConfig) => void` | Register a type. Called by the framework when `defineAppModule({ notifications })` runs — rarely needed manually. |
45
- | `ingest(msg, opts?)` | `(PushNotification, { broadcast?: boolean }?) => void` | Push a message through the same pipeline SignalR uses. Broadcasts pass through the active broadcast filter. |
46
- | `setBroadcastFilter(fn)` | `((PushNotification) => boolean) => void` | Install a filter for broadcast messages. Prefer [useBroadcastFilter](./useBroadcastFilter.md) — same method. |
47
- | `clearBroadcastFilter()` | `() => void` | Remove the broadcast filter. |
48
- | `markAsRead(msg)` | `(PushNotification) => void` | Mark one message as read. Mirrors to the server. |
49
- | `markAllAsRead()` | `() => Promise<void>` | Optimistic mark-all with rollback on failure. |
50
- | `loadHistory(take?)` | `(number?) => Promise<void>` | Fetch history from the platform. Default page size is 10. The shell already calls this at bootstrap. |
51
- | `subscribe(opts)` | `({ types, filter?, handler? }) => () => void` | Low-level pub/sub. Returns an `unsub` function. Inside blades use [useBladeNotifications](./useBladeNotifications.md) — it wraps this and registers cleanup automatically. |
52
- | `getByType(type)` | `(string) => PushNotification[]` | Filter `history` by `notifyType`. |
53
-
54
- ## Escape-hatch patterns
55
-
56
- ### Manually ingest a message (tests, scripted replay)
57
-
58
- ```ts
59
- const store = useNotificationStore();
60
-
61
- store.ingest({
62
- id: "test-1",
63
- notifyType: "OrderCreatedDomainEvent",
64
- title: "Test order",
65
- isNew: true,
66
- created: new Date().toISOString(),
67
- } as PushNotification);
68
- ```
69
-
70
- The ingest pipeline runs the configured toast logic and notifies subscribers exactly like a real SignalR message would, so you can verify the end-to-end behavior of `defineAppModule({ notifications })` + `useBladeNotifications` without a live hub.
71
-
72
- `ingest` with `{ broadcast: true }` simulates `SendSystemEvents` and runs the broadcast filter; without it the message is treated as targeted and bypasses the filter.
73
-
74
- ### Custom shell surface (replace the bell dropdown)
75
-
76
- When you are building a shell variant that omits the framework's bell dropdown — for example, a module-federation host that renders its own header — bind to the store directly:
77
-
78
- ```ts
79
- import { useNotificationStore } from "@vc-shell/framework";
80
- import { onMounted } from "vue";
81
-
82
- const store = useNotificationStore();
83
-
84
- onMounted(() => store.loadHistory(100));
85
- // Use store.history, store.unreadCount, store.markAllAsRead in your own components.
86
- ```
87
-
88
- If you are using `VcApp` (the default shell), do **not** do this — the dropdown is already mounted and binding to the same store, so a second surface duplicates what is already on screen.
89
-
90
- ## Resolution
91
-
92
- `useNotificationStore()` resolves in this order:
93
-
94
- 1. If called inside a Vue component's `setup` (or `app.runWithContext()`), it uses `inject(NotificationStoreKey)`.
95
- 2. Otherwise it returns a module-level singleton created on first call.
96
-
97
- The fallback exists so module-federation remotes and standalone scripts see the same store the host app uses.
98
-
99
- ## Tips
100
-
101
- - **Prefer facades.** Every time you find yourself writing `store.subscribe(...)` inside a blade, you want `useBladeNotifications`. Every time you write `store.setBroadcastFilter(...)`, you want `useBroadcastFilter`. The facades exist to keep cleanup, typing, and invariants in one place.
102
- - **Do not iterate `realtime` for unread counts inside a blade.** That is what `useBladeNotifications` does correctly. Touching `realtime` directly couples your component to shell internals.
103
- - **`loadHistory` replaces, then merges.** Calling it again pages in more entries and merges them with the existing history.
104
- - **`markAllAsRead` is optimistic.** The local state flips immediately; if the server call fails the change is rolled back and an error toast surfaces.
105
- - **Direct store mutations can bypass invariants.** The broadcast filter only runs through `ingest({ broadcast: true })`. Pushing arrays around `history.value` directly is not supported.
106
-
107
- ## Related
108
-
109
- - [useBladeNotifications](./useBladeNotifications.md) — recommended scope-aware subscription. **Start here.**
110
- - [useBroadcastFilter](./useBroadcastFilter.md) — broadcast acceptance gate.
111
- - [useNotificationContext](./useNotificationContext.md) — payload access inside templates.
112
- - [Notifications concept page.](../../concepts/notifications.md)
113
- - [Notifications plugin reference.](../../plugins/notifications.md)
@@ -1,123 +0,0 @@
1
- ---
2
- title: Assets Module
3
- category: reference
4
- group: modules
5
- slug: assets
6
- ---
7
-
8
- # Assets Details Module
9
-
10
- A built-in child blade for editing a single asset's metadata: display name, alt text (images only), and description. Opened by `AssetsManager` when the user clicks a table row, but also usable standalone from any parent blade that needs single-asset editing.
11
-
12
- ## Overview
13
-
14
- The blade reads an `AssetLike` instance from `options.asset`, exposes a form pre-populated from it, and delegates persistence back to the caller through two callbacks (`assetEditHandler`, `assetRemoveHandler`). The blade itself never mutates the original asset and never talks to the network — the caller decides what saving and removing means.
15
-
16
- The module is registered as `AssetsDetailsModule` and exposes the `AssetsDetails` blade (registered globally under the name `"AssetsDetails"`).
17
-
18
- ## Module Registration
19
-
20
- ```typescript
21
- import { AssetsDetailsModule } from "@vc-shell/framework";
22
-
23
- // Registered automatically when the framework loads
24
- // Exposes: AssetsDetails blade component
25
- ```
26
-
27
- ## Options (via `useBlade`)
28
-
29
- The blade reads its configuration from `options` via `useBlade<AssetsDetailsOptions>()` (not props):
30
-
31
- | Option | Type | Description |
32
- | -------------------- | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
33
- | `asset` | `AssetLike` | The asset to edit. The blade clones it into local state — the source object is never mutated until `assetEditHandler` is called. |
34
- | `disabled` | `boolean?` | When true, every input is read-only and the toolbar Save/Delete buttons are disabled. |
35
- | `hiddenFields` | `string[]?` | Field names to hide. Supported values: `"name"`, `"altText"`, `"description"`. The header (preview, size, created date, URL) is always shown. |
36
- | `assetEditHandler` | `(asset: AssetLike) => void \| Promise<void>` | Called by the toolbar Save button with the edited copy. The blade closes itself after the handler resolves. |
37
- | `assetRemoveHandler` | `(asset: AssetLike) => Promise<void>` | Called by the toolbar Delete button. The blade closes itself after the handler resolves. |
38
-
39
- ## Usage
40
-
41
- ### Direct invocation
42
-
43
- ```typescript
44
- import { useBlade } from "@vc-shell/framework";
45
-
46
- const { openBlade } = useBlade();
47
-
48
- openBlade({
49
- name: "AssetsDetails",
50
- options: {
51
- asset: selectedAsset,
52
- disabled: !canEdit.value,
53
- hiddenFields: ["altText"],
54
- assetEditHandler: async (edited) => {
55
- await api.updateAsset(edited);
56
- },
57
- assetRemoveHandler: async (asset) => {
58
- await api.deleteAsset(asset.id);
59
- },
60
- },
61
- });
62
- ```
63
-
64
- ### Used by AssetsManager
65
-
66
- When `AssetsManager` opens this blade on row click, it wires the manager's mutation methods into the handlers automatically:
67
-
68
- ```typescript
69
- // inside AssetsManager
70
- openBlade({
71
- name: "AssetsDetails",
72
- options: {
73
- asset,
74
- disabled: readonly.value,
75
- hiddenFields: options.value?.hiddenFields,
76
- assetEditHandler: (asset) => manager.updateItem(asset),
77
- assetRemoveHandler: (asset) => manager.remove(asset),
78
- },
79
- });
80
- ```
81
-
82
- ## Header
83
-
84
- The header is always rendered above the editable form and is not configurable through `hiddenFields`:
85
-
86
- | Element | Source |
87
- | ------------ | -------------------------------------------------------------------------------------------------- |
88
- | Preview | `VcImage` thumbnail for image extensions (png/jpg/jpeg/svg/gif), colored extension badge otherwise |
89
- | Size | `readableSize(asset.size)` — formatted as `KB`/`MB`/`GB` |
90
- | Created date | `asset.createdDate` rendered with `type="date-ago"` |
91
- | URL | `asset.url` displayed as a copyable link with `asset.name` as the visible label |
92
-
93
- ## Form fields
94
-
95
- All fields are bound to a local clone of `asset`. They can be hidden individually via `hiddenFields`.
96
-
97
- | Field | Visibility | Notes |
98
- | ------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
99
- | `name` | Hidden if `hiddenFields` includes `"name"` | Required. Edits the **base name only** — the original extension is preserved on save (e.g. editing `report.pdf` keeps `.pdf`). |
100
- | `altText` | Image assets only; hide via `"altText"` | Plain string. Shown only when `asset.typeId === "Image"`. |
101
- | `description` | Hidden if `hiddenFields` includes `"description"` | Multiline textarea. |
102
-
103
- ## Toolbar
104
-
105
- | Button | Disabled condition | Action |
106
- | ------ | ------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
107
- | Save | `disabled === true`, the form is invalid, or the form is not dirty (vee-validate `useIsFormValid`/`useIsFormDirty`) | `await assetEditHandler(editedAsset)`, then `closeSelf()` |
108
- | Delete | `disabled === true` | `await assetRemoveHandler(editedAsset)`, then `closeSelf()` |
109
-
110
- ## Tips
111
-
112
- - **Extension is locked.** The `name` input only edits the base name; the original extension is reattached on save. Renaming `photo.png` to `cover` produces `cover.png`.
113
- - **Required name validation** uses vee-validate's `required` rule. The Save button stays disabled until validation passes.
114
- - **`assetEditHandler`** is invoked with the edited clone — the original `options.asset` reference is never mutated. Callers should treat the argument as the new state.
115
- - **`disabled` makes the blade fully read-only:** inputs are disabled and toolbar Save/Delete are disabled regardless of dirtiness.
116
- - **`hiddenFields` does not enforce required-ness.** Hiding `"name"` skips the validated `<Field>`, so the Save button only depends on form dirtiness.
117
-
118
- ## Related
119
-
120
- - `framework/modules/assets-manager/` -- parent `AssetsManager` blade that opens `AssetsDetails` on row click
121
- - `framework/core/composables/useAssetsManager/` -- `AssetLike` type definition
122
- - `framework/core/utilities/assets.ts` -- `isImage`, `readableSize`, `getExtensionColor`, `getExtensionLabel`
123
- - `framework/ui/components/molecules/vc-field/` -- `VcField` used by the header for size/date/url
@@ -1,33 +0,0 @@
1
- ---
2
- title: DashboardWidgetSkeleton
3
- category: composables
4
- group: utilities
5
- internal: true
6
- ---
7
-
8
- # DashboardWidgetSkeleton
9
-
10
- Internal placeholder card used by `GridstackDashboard` while remote modules are still loading. Mimics the shape of `DashboardWidgetCard` (header with icon + title, stats row, content lines) with a shimmer animation. Not exported from `@vc-shell/framework` — consumed only inside the dashboard organism.
11
-
12
- ## When to Use
13
-
14
- - You won't use this directly. `GridstackDashboard` renders one per `SkeletonItem` while `ModulesReadyKey` resolves to `false`.
15
- - If you build a custom dashboard layout and want the same loading aesthetic, copy this file rather than importing it — the component is intentionally internal so the dashboard team can change its markup at any time.
16
-
17
- ## Layout Contract
18
-
19
- The skeleton has no props. Its parent positions it via inline `style` (`grid-column` / `grid-row`) inside a 12-column CSS grid. Card height/width is fully determined by the grid cell it occupies, so the skeleton stretches to fill its slot.
20
-
21
- ## Accessibility
22
-
23
- - The wrapper has `aria-hidden="true"` so screen readers ignore the visual placeholders. The parent grid carries `role="status"` + `aria-busy="true"` to announce loading state once.
24
- - Shimmer animation is disabled when the user prefers reduced motion.
25
-
26
- ## Design Tokens
27
-
28
- Skeleton inherits dashboard card tokens where possible (`--dashboard-widget-card-background`, `--dashboard-widget-card-border-color`, `--dashboard-widget-card-border-radius`, `--dashboard-widget-card-shadow`) so its silhouette matches the real card. Shimmer colors are read from `--neutrals-100` / `--neutrals-200`.
29
-
30
- ## Related
31
-
32
- - [DraggableDashboard](./draggable-dashboard.docs.md) — owns the skeleton grid and decides when to render placeholders.
33
- - [DashboardWidgetCard](../dashboard-widget-card/dashboard-widget-card.docs.md) — the real card whose shape skeletons imitate.