@supersoniks/concorde 4.6.0 → 4.7.0

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 (179) hide show
  1. package/.gitlab-ci.yml +23 -0
  2. package/README.md +106 -55
  3. package/ai/AGENTS.md +52 -0
  4. package/ai/README.md +30 -0
  5. package/ai/cursor/rules/concorde-menu.mdc +15 -0
  6. package/ai/cursor/rules/concorde-scope.mdc +14 -0
  7. package/ai/cursor/rules/concorde-theme.mdc +13 -0
  8. package/ai/cursor/rules/concorde.mdc +49 -0
  9. package/ai/jetbrains/rules/concorde.md +39 -0
  10. package/ai/skills/concorde/SKILL.md +220 -0
  11. package/ai/skills/concorde-get-set-dp/SKILL.md +194 -0
  12. package/ai/skills/concorde-imports/SKILL.md +78 -0
  13. package/ai/skills/concorde-menu/SKILL.md +74 -0
  14. package/ai/skills/concorde-scope/SKILL.md +70 -0
  15. package/ai/skills/concorde-theme/SKILL.md +46 -0
  16. package/build-infos.json +1 -1
  17. package/concorde-core.bundle.js +127 -127
  18. package/concorde-core.es.js +1435 -1364
  19. package/dist/altcha-widget.js +2662 -0
  20. package/dist/concorde-core.bundle.js +127 -127
  21. package/dist/concorde-core.es.js +1435 -1364
  22. package/dist/docs-mock-api-sw.js +589 -0
  23. package/dist/docs-mock-api-sw.js.map +7 -0
  24. package/docs/altcha-widget.js +2662 -0
  25. package/docs/assets/index-D9pxaQYK.js +7508 -0
  26. package/docs/assets/index-t0-i22oI.css +1 -0
  27. package/docs/docs-mock-api-sw.js +589 -0
  28. package/docs/docs-mock-api-sw.js.map +7 -0
  29. package/docs/index.html +2 -2
  30. package/docs/src/core/components/functional/fetch/fetch.md +13 -11
  31. package/docs/src/core/components/functional/if/if.md +4 -11
  32. package/docs/src/core/components/functional/list/list.md +60 -194
  33. package/docs/src/core/components/functional/queue/queue.md +70 -85
  34. package/docs/src/core/components/functional/router/router.md +62 -97
  35. package/docs/src/core/components/functional/states/states.md +2 -2
  36. package/docs/src/core/components/functional/submit/submit.md +86 -55
  37. package/docs/src/core/components/ui/captcha/captcha.md +2 -2
  38. package/docs/src/core/components/ui/card/card.md +1 -1
  39. package/docs/src/core/components/ui/form/checkbox/checkbox.md +5 -32
  40. package/docs/src/core/components/ui/form/input/input.md +5 -30
  41. package/docs/src/core/components/ui/form/input-autocomplete/input-autocomplete.md +6 -4
  42. package/docs/src/core/components/ui/form/radio/radio.md +5 -32
  43. package/docs/src/core/components/ui/form/select/select.md +5 -31
  44. package/docs/src/core/components/ui/form/switch/switch.md +5 -32
  45. package/docs/src/core/components/ui/loader/loader.md +1 -13
  46. package/docs/src/core/components/ui/table/table.md +3 -3
  47. package/docs/src/docs/_core-concept/dataFlow.md +73 -0
  48. package/docs/src/docs/_core-concept/subscriber.md +9 -10
  49. package/docs/src/docs/_decorators/ancestor-attribute.md +4 -3
  50. package/docs/src/docs/_decorators/auto-subscribe.md +19 -16
  51. package/docs/src/docs/_decorators/bind.md +20 -17
  52. package/docs/src/docs/_decorators/get.md +7 -4
  53. package/docs/src/docs/_decorators/handle.md +171 -0
  54. package/docs/src/docs/_decorators/on-assign.md +99 -73
  55. package/docs/src/docs/_decorators/publish.md +2 -1
  56. package/docs/src/docs/_decorators/subscribe.md +70 -9
  57. package/docs/src/docs/_decorators/wait-for-ancestors.md +13 -10
  58. package/docs/src/docs/_directives/sub.md +91 -0
  59. package/docs/src/docs/_getting-started/ai-agents.md +56 -0
  60. package/docs/src/docs/_getting-started/concorde-manual-install.md +133 -0
  61. package/docs/src/docs/_getting-started/concorde-outside.md +13 -123
  62. package/docs/src/docs/_getting-started/create-a-component.md +2 -0
  63. package/docs/src/docs/_getting-started/my-first-component.md +236 -0
  64. package/docs/src/docs/_getting-started/my-first-subscriber.md +29 -83
  65. package/docs/src/docs/_getting-started/pubsub.md +21 -134
  66. package/docs/src/docs/_getting-started/start.md +26 -18
  67. package/docs/src/docs/_misc/api-configuration.md +79 -0
  68. package/docs/src/docs/_misc/dataProviderKey.md +38 -5
  69. package/docs/src/docs/_misc/docs-mock-api.md +60 -0
  70. package/docs/src/docs/_misc/endpoint.md +2 -1
  71. package/docs/src/docs/_misc/html-integration.md +13 -0
  72. package/docs/src/docs/search/docs-search.json +4163 -873
  73. package/docs/src/tsconfig.json +380 -317
  74. package/gitlab/job_tests.sh +55 -0
  75. package/package.json +34 -3
  76. package/public/altcha-widget.js +2662 -0
  77. package/public/docs-mock-api-sw.js +589 -0
  78. package/public/docs-mock-api-sw.js.map +7 -0
  79. package/scripts/ai-init.mjs +167 -0
  80. package/scripts/docs-mock-api-vite-plugin.ts +116 -0
  81. package/scripts/docs-open-in-editor-plugin.ts +130 -0
  82. package/scripts/pre-publish.mjs +2 -1
  83. package/src/core/components/functional/example/example.ts +1 -1
  84. package/src/core/components/functional/fetch/fetch.md +13 -11
  85. package/src/core/components/functional/if/if.md +4 -11
  86. package/src/core/components/functional/list/list.demo.ts +4 -4
  87. package/src/core/components/functional/list/list.md +60 -194
  88. package/src/core/components/functional/list/list.ts +8 -7
  89. package/src/core/components/functional/queue/queue.demo.ts +1 -1
  90. package/src/core/components/functional/queue/queue.md +70 -85
  91. package/src/core/components/functional/queue/queue.ts +4 -4
  92. package/src/core/components/functional/router/router.md +62 -97
  93. package/src/core/components/functional/router/router.ts +1 -1
  94. package/src/core/components/functional/states/states.md +2 -2
  95. package/src/core/components/functional/submit/submit.md +86 -55
  96. package/src/core/components/functional/submit/submit.ts +10 -3
  97. package/src/core/components/ui/captcha/captcha.md +2 -2
  98. package/src/core/components/ui/card/card.md +1 -1
  99. package/src/core/components/ui/form/checkbox/checkbox.md +5 -32
  100. package/src/core/components/ui/form/input/input.md +5 -30
  101. package/src/core/components/ui/form/input-autocomplete/input-autocomplete.md +6 -4
  102. package/src/core/components/ui/form/radio/radio.md +5 -32
  103. package/src/core/components/ui/form/select/select.md +5 -31
  104. package/src/core/components/ui/form/switch/switch.md +5 -32
  105. package/src/core/components/ui/loader/loader.md +1 -13
  106. package/src/core/components/ui/table/table.md +3 -3
  107. package/src/core/directives/DataProvider.sub.spec.ts +96 -0
  108. package/src/core/directives/DataProvider.ts +109 -40
  109. package/src/core/utils/PublisherProxy.ts +33 -18
  110. package/src/core/utils/dataProviderKey.ts +23 -0
  111. package/src/core/utils/publisherPathKey.spec.ts +58 -0
  112. package/src/docs/_core-concept/dataFlow.md +73 -0
  113. package/src/docs/_core-concept/subscriber.md +9 -10
  114. package/src/docs/_decorators/ancestor-attribute.md +4 -3
  115. package/src/docs/_decorators/auto-subscribe.md +19 -16
  116. package/src/docs/_decorators/bind.md +19 -16
  117. package/src/docs/_decorators/get.md +7 -4
  118. package/src/docs/_decorators/handle.md +15 -13
  119. package/src/docs/_decorators/on-assign.md +53 -53
  120. package/src/docs/_decorators/publish.md +2 -1
  121. package/src/docs/_decorators/subscribe.md +70 -9
  122. package/src/docs/_decorators/wait-for-ancestors.md +13 -10
  123. package/src/docs/_directives/sub.md +91 -0
  124. package/src/docs/_getting-started/ai-agents.md +56 -0
  125. package/src/docs/_getting-started/concorde-manual-install.md +133 -0
  126. package/src/docs/_getting-started/concorde-outside.md +13 -123
  127. package/src/docs/_getting-started/create-a-component.md +2 -0
  128. package/src/docs/_getting-started/my-first-component.md +236 -0
  129. package/src/docs/_getting-started/my-first-subscriber.md +29 -83
  130. package/src/docs/_getting-started/pubsub.md +21 -134
  131. package/src/docs/_getting-started/start.md +26 -18
  132. package/src/docs/_misc/api-configuration.md +79 -0
  133. package/src/docs/_misc/dataProviderKey.md +34 -1
  134. package/src/docs/_misc/docs-mock-api.md +60 -0
  135. package/src/docs/_misc/endpoint.md +2 -1
  136. package/src/docs/_misc/html-integration.md +13 -0
  137. package/src/docs/code.ts +58 -12
  138. package/src/docs/components/docs-demo-sources.ts +397 -0
  139. package/src/docs/components/docs-lit-demo-raw.ts +28 -0
  140. package/src/docs/components/docs-lit-demo.ts +166 -0
  141. package/src/docs/components/docs-source-link.ts +72 -0
  142. package/src/docs/docs-location.ts +54 -0
  143. package/src/docs/docs.ts +12 -0
  144. package/src/docs/example/decorators-demo-bind-demos.ts +41 -46
  145. package/src/docs/example/decorators-demo-geo.ts +16 -11
  146. package/src/docs/example/decorators-demo-init.ts +2 -228
  147. package/src/docs/example/decorators-demo-subscribe-publish-get-demos.ts +54 -14
  148. package/src/docs/example/decorators-demo.ts +71 -70
  149. package/src/docs/example/docs-api-config-demos.ts +234 -0
  150. package/src/docs/example/docs-joke-demos.ts +297 -0
  151. package/src/docs/example/docs-list-demos.ts +179 -0
  152. package/src/docs/example/docs-provider-keys.ts +315 -0
  153. package/src/docs/example/docs-queue-demos.ts +114 -0
  154. package/src/docs/example/docs-router-demos.ts +89 -0
  155. package/src/docs/example/docs-submit-demos.ts +455 -0
  156. package/src/docs/example/docs-toggle-demos.ts +73 -0
  157. package/src/docs/example/docs-user-two-scopes.ts +37 -0
  158. package/src/docs/example/docs-users-list.ts +71 -0
  159. package/src/docs/example/users.ts +41 -24
  160. package/src/docs/mock-api/api-config-mock.ts +152 -0
  161. package/src/docs/mock-api/fixtures.ts +377 -0
  162. package/src/docs/mock-api/register.ts +25 -0
  163. package/src/docs/mock-api/router.ts +234 -0
  164. package/src/docs/mock-api/service-worker.ts +23 -0
  165. package/src/docs/mock-api/urls.ts +11 -0
  166. package/src/docs/navigation/navigation.ts +39 -7
  167. package/src/docs/search/docs-search.json +4021 -936
  168. package/src/docs/search/markdown-renderer.ts +7 -3
  169. package/src/docs/search/page.ts +11 -14
  170. package/src/docs/search/sonic-code-markdown.spec.ts +29 -0
  171. package/src/docs/search/sonic-code-markdown.ts +28 -0
  172. package/src/docs.ts +4 -0
  173. package/src/tsconfig.json +87 -0
  174. package/src/tsconfig.tsbuildinfo +1 -1
  175. package/vite.config.mts +8 -0
  176. package/docs/assets/index-CaysOMFz.js +0 -5046
  177. package/docs/assets/index-D8mGoXzF.css +0 -1
  178. package/docs/src/docs/_misc/templates-demo.md +0 -19
  179. package/src/docs/_misc/templates-demo.md +0 -19
@@ -1,7 +1,6 @@
1
1
  # @onAssign
2
2
 
3
- > **⚠️ Deprecated use [@handle](#docs/_decorators/handle.md/handle) instead.**
4
- > `@onAssign` takes untyped **string** paths; `@handle` does the same with full typing via `DataProviderKey`, supports up to 3 keys, and exposes explicit options. `@onAssign` stays functional and unchanged during the migration. See the **Migrating to @handle** section below.
3
+ > **New apps:** use [@handle](#docs/_decorators/handle.md/handle) with `DataProviderKey` ([Data flow](#docs/_core-concept/dataFlow.md/dataFlow)). `@onAssign` uses untyped string paths; it remains documented for existing codebases — see **Migrating to @handle** below.
5
4
 
6
5
  The `@onAssign` decorator allows you to execute a method when one or more publishers are updated. The method is called only when all specified publishers have been assigned values.
7
6
 
@@ -9,7 +8,7 @@ For a **typed** equivalent (recommended), use [@handle](#docs/_decorators/handle
9
8
 
10
9
  ## Principle
11
10
 
12
- This decorator subscribes to one or more publishers via the `PublisherManager`. When all specified publishers have been assigned values (via `set`), the decorated method is called with all the values as arguments.
11
+ This decorator subscribes to one or more publishers by **string path** (legacy). When all specified publishers have been assigned values (via `set`), the decorated method is called with all the values as arguments. Prefer [@handle](#docs/_decorators/handle.md/handle) + `DataProviderKey` and `get` / `set` from [Data flow](#docs/_core-concept/dataFlow.md/dataFlow).
13
12
 
14
13
  This is particularly useful when you need to wait for multiple data sources to be ready before executing logic.
15
14
 
@@ -32,11 +31,11 @@ import { onAssign } from "@supersoniks/concorde/decorators";
32
31
  @customElement("demo-on-assign")
33
32
  export class DemoOnAssign extends LitElement {
34
33
  static styles = [tailwind];
35
- //
34
+
36
35
  @state() userWithSettings: any = null;
37
36
  @state() isReady: boolean = false;
38
37
  @state() lastUpdate: string = "";
39
- //
38
+
40
39
  @onAssign("demoUser", "demoUserSettings")
41
40
  handleDataReady(user: any, settings: any) {
42
41
  this.isReady = Object.keys(user).length > 0 && Object.keys(settings).length > 0;
@@ -44,12 +43,12 @@ export class DemoOnAssign extends LitElement {
44
43
  this.lastUpdate = new Date().toLocaleTimeString();
45
44
  this.requestUpdate();
46
45
  }
47
- //
46
+
48
47
  render() {
49
48
  const { name, email, theme, language } = this.userWithSettings;
50
49
  return //...
51
50
  }
52
- //
51
+
53
52
  updateData() {
54
53
  const user = PublisherManager.get("demoUser");
55
54
  const userSettings = PublisherManager.get("demoUserSettings");
@@ -58,7 +57,7 @@ export class DemoOnAssign extends LitElement {
58
57
  name: `User n°${userNumber}`,
59
58
  email: `user-${userNumber}@example.com`,
60
59
  });
61
- //
60
+
62
61
  userSettings.set({
63
62
  theme: ["light", "dark", "auto"][Math.floor(Math.random() * 3)],
64
63
  language: ["en", "fr", "es"][Math.floor(Math.random() * 3)],
@@ -70,6 +69,7 @@ export class DemoOnAssign extends LitElement {
70
69
 
71
70
  <sonic-code>
72
71
  <template>
72
+ <docs-demo-sources for="demo-on-assign"></docs-demo-sources>
73
73
  <demo-on-assign></demo-on-assign>
74
74
  </template>
75
75
  </sonic-code>
@@ -83,17 +83,17 @@ export class DemoOnAssign extends LitElement {
83
83
  export class ProductView extends LitElement {
84
84
  product: any = null;
85
85
  inventory: any = null;
86
- //
86
+
87
87
  @onAssign("store.product", "store.inventory")
88
88
  handleProductData(product: any, inventory: any) {
89
89
  this.product = product;
90
90
  this.inventory = inventory;
91
91
  this.requestUpdate();
92
92
  }
93
- //
93
+
94
94
  render() {
95
95
  if (!this.product) return html`<div>Loading...</div>`;
96
- //
96
+
97
97
  const stock = this.inventory[this.product.id] || 0;
98
98
  return html`
99
99
  <div>
@@ -134,32 +134,32 @@ Each placeholder is replaced at runtime with the current value of the correspond
134
134
  @customElement("demo-on-assign-dynamic")
135
135
  export class DemoOnAssignDynamic extends LitElement {
136
136
  static styles = [tailwind];
137
- //
137
+
138
138
  @property({ type: String })
139
139
  dataProvider: "demoUsers" | "demoUsersAlt" = "demoUsers";
140
- //
140
+
141
141
  @property({ type: Number })
142
142
  userIndex: number = 0;
143
- //
143
+
144
144
  @state() user: any = null;
145
145
  @state() userSettings: any = null;
146
- //
146
+
147
147
  @onAssign("${dataProvider}.${userIndex}", "${dataProvider}Settings.${userIndex}")
148
148
  handleUserDataReady(user: any, settings: any) {
149
149
  this.user = user;
150
150
  this.userSettings = settings;
151
151
  }
152
- //
152
+
153
153
  updateUserIndex(e: Event) {
154
154
  this.userIndex = parseInt((e.target as HTMLInputElement).value);
155
155
  }
156
- //
156
+
157
157
  updateDataProvider(e: Event) {
158
158
  this.dataProvider = (e.target as HTMLSelectElement).value as
159
159
  | "demoUsers"
160
160
  | "demoUsersAlt";
161
161
  }
162
- //
162
+
163
163
  updateCurrentUserData() {
164
164
  const usersPublisher = PublisherManager.get(this.dataProvider);
165
165
  const settingsPublisher = PublisherManager.get(
@@ -173,7 +173,7 @@ export class DemoOnAssignDynamic extends LitElement {
173
173
  settingsPublisher,
174
174
  [String(this.userIndex)]
175
175
  ) as PublisherProxy;
176
- //
176
+
177
177
  if (userPublisher && settingPublisher) {
178
178
  // Générer de nouvelles données aléatoires
179
179
  const randomNames = [
@@ -183,7 +183,7 @@ export class DemoOnAssignDynamic extends LitElement {
183
183
  ];
184
184
  const randomThemes = ["light", "dark", "auto"];
185
185
  const randomLanguages = ["en", "fr", "es"];
186
- //
186
+
187
187
  const randomName =
188
188
  randomNames[Math.floor(Math.random() * randomNames.length)];
189
189
  const randomEmail = `${randomName.firstName.toLowerCase()}.${randomName.lastName.toLowerCase()}@example.com`;
@@ -191,7 +191,7 @@ export class DemoOnAssignDynamic extends LitElement {
191
191
  randomThemes[Math.floor(Math.random() * randomThemes.length)];
192
192
  const randomLanguage =
193
193
  randomLanguages[Math.floor(Math.random() * randomLanguages.length)];
194
- //
194
+
195
195
  // Mettre à jour l'utilisateur directement
196
196
  const currentUser = userPublisher.get() || {};
197
197
  userPublisher.set({
@@ -200,7 +200,7 @@ export class DemoOnAssignDynamic extends LitElement {
200
200
  lastName: randomName.lastName,
201
201
  email: randomEmail,
202
202
  });
203
- //
203
+
204
204
  // Mettre à jour les settings directement
205
205
  settingPublisher.set({
206
206
  theme: randomTheme,
@@ -208,15 +208,15 @@ export class DemoOnAssignDynamic extends LitElement {
208
208
  });
209
209
  }
210
210
  }
211
- //
211
+
212
212
  render() {
213
213
  return html`
214
- <div class="flex flex-col gap-2">
215
- <sonic-select label="Users set" @change=${this.updateDataProvider}>
216
- <option value="demoUsers">First set of users</option>
217
- <option value="demoUsersAlt">Second set of users</option>
218
- </sonic-select>
219
- <sonic-input
214
+ &lt;div class="flex flex-col gap-2"&gt;
215
+ &lt;sonic-select label="Users set" @change=${this.updateDataProvider}&gt;
216
+ &lt;option value="demoUsers"&gt;First set of users&lt;/option&gt;
217
+ &lt;option value="demoUsersAlt"&gt;Second set of users&lt;/option&gt;
218
+ &lt;/sonic-select&gt;
219
+ &lt;sonic-input
220
220
  type="number"
221
221
  .value=${this.userIndex}
222
222
  @input=${this.updateUserIndex}
@@ -224,26 +224,25 @@ export class DemoOnAssignDynamic extends LitElement {
224
224
  max="9"
225
225
  label="Index"
226
226
  class="block"
227
- >
228
- </sonic-input>
229
- <sonic-button @click=${this.updateCurrentUserData}
230
- >Update current user data</sonic-button
231
- >
232
- <div class="flex flex-col gap-2 border p-2">
233
- <div>
234
- <sonic-icon name="user" library="heroicons"></sonic-icon>
227
+ &gt;&lt;/sonic-input&gt;
228
+ &lt;sonic-button @click=${this.updateCurrentUserData}&gt;
229
+ Update current user data
230
+ &lt;/sonic-button&gt;
231
+ &lt;div class="flex flex-col gap-2 border p-2"&gt;
232
+ &lt;div&gt;
233
+ &lt;sonic-icon name="user" library="heroicons"&gt;&lt;/sonic-icon&gt;
235
234
  ${this.user?.firstName} ${this.user?.lastName}
236
- </div>
237
- <div>
238
- <sonic-icon name="envelope" library="heroicons"></sonic-icon>
235
+ &lt;/div&gt;
236
+ &lt;div&gt;
237
+ &lt;sonic-icon name="envelope" library="heroicons"&gt;&lt;/sonic-icon&gt;
239
238
  ${this.user?.email}
240
- </div>
241
- <div>
239
+ &lt;/div&gt;
240
+ &lt;div&gt;
242
241
  Theme: ${this.userSettings?.theme} | Language:
243
242
  ${this.userSettings?.language}
244
- </div>
245
- </div>
246
- </div>
243
+ &lt;/div&gt;
244
+ &lt;/div&gt;
245
+ &lt;/div&gt;
247
246
  `;
248
247
  }
249
248
  }
@@ -252,6 +251,7 @@ export class DemoOnAssignDynamic extends LitElement {
252
251
 
253
252
  <sonic-code>
254
253
  <template>
254
+ <docs-demo-sources for="demo-on-assign-dynamic"></docs-demo-sources>
255
255
  <demo-on-assign-dynamic></demo-on-assign-dynamic>
256
256
  </template>
257
257
  </sonic-code>
@@ -290,13 +290,13 @@ import { html, LitElement } from "lit";
290
290
  import { customElement } from "lit/decorators.js";
291
291
  import { onAssign } from "@supersoniks/concorde/decorators";
292
292
  import { PublisherManager } from "@supersoniks/concorde/core/utils/PublisherProxy";
293
- //
293
+
294
294
  @customElement("order-summary")
295
295
  export class OrderSummary extends LitElement {
296
296
  order: any = null;
297
297
  customer: any = null;
298
298
  shipping: any = null;
299
- //
299
+
300
300
  @onAssign("orderData", "customerData", "shippingData")
301
301
  handleOrderReady(order: any, customer: any, shipping: any) {
302
302
  this.order = order;
@@ -304,12 +304,12 @@ export class OrderSummary extends LitElement {
304
304
  this.shipping = shipping;
305
305
  this.requestUpdate();
306
306
  }
307
- //
307
+
308
308
  render() {
309
309
  if (!this.order || !this.customer || !this.shipping) {
310
310
  return html`<div>Loading order details...</div>`;
311
311
  }
312
- //
312
+
313
313
  return html`
314
314
  <div class="order-summary">
315
315
  <h2>Order #${this.order.id}</h2>
@@ -349,11 +349,11 @@ shippingPub.set({ address: "123 Main St" });
349
349
  // Before
350
350
  @onAssign("demoUser", "demoUserSettings")
351
351
  handleDataReady(user: any, settings: any) { /* ... */ }
352
- //
352
+
353
353
  // After — same "wait for everything" behavior, but typed
354
354
  const user = new DataProviderKey&lt;User&gt;("demoUser");
355
355
  const settings = new DataProviderKey&lt;Settings&gt;("demoUserSettings");
356
- //
356
+
357
357
  @handle(user, settings, { waitForAllDefined: true })
358
358
  handleDataReady(user: User, settings: Settings) { /* ... */ }
359
359
  </template>
@@ -366,10 +366,10 @@ handleDataReady(user: User, settings: Settings) { /* ... */ }
366
366
  // Before
367
367
  @onAssign("settings.modules.logs_route.enabled")
368
368
  onLogRoute(value: boolean) { /* ... */ }
369
- //
369
+
370
370
  // After
371
371
  const settings = new DataProviderKey&lt;AppSettings&gt;("settings");
372
- //
372
+
373
373
  @handle(settings.modules.logs_route.enabled)
374
374
  onLogRoute(value: boolean) { /* ... */ }
375
375
  </template>
@@ -38,7 +38,7 @@ export class DemoPublish extends LitElement {
38
38
  @input=${(e) => (this.email = (e.target as HTMLInputElement).value)}
39
39
  label="Email"
40
40
  ></sonic-input>
41
- <p>${sub("publishDemo.email")}</p>
41
+ <p>${sub(publishDemoKey.email)}</p>
42
42
  `;
43
43
  }
44
44
  }
@@ -47,6 +47,7 @@ export class DemoPublish extends LitElement {
47
47
 
48
48
  <sonic-code>
49
49
  <template>
50
+ <docs-demo-sources for="demo-publish"></docs-demo-sources>
50
51
  <demo-publish></demo-publish>
51
52
  </template>
52
53
  </sonic-code>
@@ -1,8 +1,17 @@
1
1
  # @subscribe
2
2
 
3
- Read-only binding: **only** `DataProviderKey<T>` (no legacy string path). No `reflect` option the publisher updates the property, not the other way around.
3
+ Keeps a **Lit property** in sync with a **read-only** slice of the DataProvider store. You pass a [`DataProviderKey`](#docs/_misc/dataProviderKey.md/dataProviderKey); when that path changes, the property updates and the component re-renders.
4
4
 
5
- For bidirectional binding or string paths, use [@bind](#docs/_decorators/bind.md/bind).
5
+ Typical setup (same idea as [My first component](#docs/_getting-started/my-first-component.md/my-first-component)):
6
+
7
+ | Piece | Role |
8
+ |-------|------|
9
+ | **Type** `T` | Shape of the object at that path (`DocsUserData`, `{ count: number }`, …) |
10
+ | **Key** | `DataProviderKey<T, U>` — static path (`"cart"`) or dynamic (`"users.${userIndex}"`, `"${dataProvider}"`) |
11
+ | **Scope on the host** | Properties listed in `U` (e.g. `dataProvider`, `userIndex`) — often filled via [`@ancestorAttribute`](#docs/_decorators/ancestor-attribute.md/ancestor-attribute) |
12
+ | **`@subscribe(key)`** | Mirrors the store into `@state()` (or another property); read-only from the component side |
13
+
14
+ For **writing** back to the store from component state, use [@publish](#docs/_decorators/publish.md/publish). In templates, the same paths work with [sub()](#docs/_directives/sub.md/sub).
6
15
 
7
16
  ## Import
8
17
 
@@ -10,27 +19,79 @@ For bidirectional binding or string paths, use [@bind](#docs/_decorators/bind.md
10
19
  <template>
11
20
  import { subscribe } from "@supersoniks/concorde/decorators";
12
21
  import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
13
- //
22
+
14
23
  type Data = { count: number };
15
- const dataKey = new DataProviderKey<Data>("data");
16
- //
24
+ const dataKey = new DataProviderKey&lt;Data&gt;("data");
25
+
17
26
  @subscribe(dataKey.count)
18
27
  @state()
19
28
  count = 0;
20
29
  </template>
21
30
  </sonic-code>
22
31
 
23
- ## Highlights
32
+ ## Static path
24
33
 
25
- - Strict typing: the property type must match `T`.
26
- - Dynamic paths: placeholders in `DataProviderKey`, resolved from the host component’s properties.
34
+ The key path is fixed. The property type must match `T` at that segment.
35
+
36
+ <sonic-code language="typescript">
37
+ <template>
38
+ const cartKey = new DataProviderKey&lt;{ items: string[] }&gt;("cart");
39
+
40
+ @subscribe(cartKey)
41
+ @state()
42
+ cart: { items: string[] } | null = null;
43
+ </template>
44
+ </sonic-code>
45
+
46
+ ## Dynamic path and scope
47
+
48
+ Placeholders `${prop}` in the key string are resolved from **properties on the same component**. Declare them in the key’s second generic so TypeScript expects them on the host:
49
+
50
+ <sonic-code language="typescript">
51
+ <template>
52
+ type User = { firstName: string; lastName: string; email: string };
53
+
54
+ @subscribe(new DataProviderKey&lt;User, { userIndex: number }&gt;("demoUsers.${userIndex}"))
55
+ @state()
56
+ user: User | null = null;
57
+
58
+ @property({ type: Number }) userIndex = 0;
59
+ </template>
60
+ </sonic-code>
61
+
62
+ When `userIndex` changes, `@subscribe` re-resolves the path and refreshes `user`.
63
+
64
+ ## Row / ancestor scope
65
+
66
+ List items (and wrappers like `<div dataProvider="…">`) set which branch the child reads. Pattern from the tutorial:
67
+
68
+ <sonic-code language="typescript">
69
+ <template>
70
+ export const rowKey = new DataProviderKey&lt;
71
+ User,
72
+ { dataProvider: string | null }
73
+ &gt;("${dataProvider}");
74
+
75
+ @ancestorAttribute("dataProvider")
76
+ dataProvider: string | null = null;
77
+
78
+ @subscribe(rowKey)
79
+ @state()
80
+ user: User | null = null;
81
+ </template>
82
+ </sonic-code>
27
83
 
28
84
  ## Demo
29
85
 
30
86
  <sonic-code>
31
87
  <template>
88
+ <docs-demo-sources for="demo-subscribe-dynamic"></docs-demo-sources>
32
89
  <demo-subscribe-dynamic></demo-subscribe-dynamic>
33
90
  </template>
34
91
  </sonic-code>
35
92
 
36
- See also [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey).
93
+ ## See also
94
+
95
+ - [Data flow](#docs/_core-concept/dataFlow.md/dataFlow) — overview
96
+ - [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey) — paths and host generics
97
+ - [sub()](#docs/_directives/sub.md/sub) — same paths inside `html` templates
@@ -30,7 +30,7 @@ The parent is registered via `customElements.define()` (vanilla JS) rather than
30
30
  <template>
31
31
  import { html, LitElement } from "lit";
32
32
  import { customElement, state } from "lit/decorators.js";
33
- //
33
+
34
34
  // Parent: registered later via customElements.define(), not @customElement
35
35
  @dispatchConnectedEvent()
36
36
  export class DemoWaitAncestorContainer extends LitElement {
@@ -38,21 +38,21 @@ export class DemoWaitAncestorContainer extends LitElement {
38
38
  return html`<slot></slot>`;
39
39
  }
40
40
  }
41
- //
41
+
42
42
  // Child: waits for parent before initializing
43
43
  @customElement("demo-wait-ancestor-value")
44
44
  @awaitConnectedAncestors("demo-wait-ancestor-container[dataProvider]")
45
45
  export class DemoWaitAncestorValue extends LitElement {
46
46
  @ancestorAttribute("dataProvider")
47
47
  dataProvider: string | null = null;
48
- //
48
+
49
49
  @state() initializedAt: string = "";
50
- //
50
+
51
51
  connectedCallback() {
52
52
  super.connectedCallback();
53
53
  this.initializedAt = new Date().toISOString();
54
54
  }
55
- //
55
+
56
56
  render() {
57
57
  return html`
58
58
  <p>DataProvider from ancestor: <strong>${this.dataProvider || "—"}</strong></p>
@@ -60,7 +60,7 @@ export class DemoWaitAncestorValue extends LitElement {
60
60
  `;
61
61
  }
62
62
  }
63
- //
63
+
64
64
  // Demo section: register parent via customElements.define() when user clicks
65
65
  @customElement("demo-wait-ancestors-section")
66
66
  export class DemoWaitAncestorsSection extends LitElement {
@@ -83,7 +83,8 @@ export class DemoWaitAncestorsSection extends LitElement {
83
83
 
84
84
  <sonic-code>
85
85
  <template>
86
- <demo-wait-ancestors-section></demo-wait-ancestors-section>
86
+ <docs-demo-sources for="demo-wait-ancestors-section"></docs-demo-sources>
87
+ <demo-wait-ancestors-section></demo-wait-ancestors-section>
87
88
  </template>
88
89
  </sonic-code>
89
90
 
@@ -105,7 +106,8 @@ When the parent is defined at load and already in the DOM, the child initializes
105
106
 
106
107
  <sonic-code>
107
108
  <template>
108
- <demo-wait-ancestors-static-section></demo-wait-ancestors-static-section>
109
+ <docs-demo-sources for="demo-wait-ancestors-static-section"></docs-demo-sources>
110
+ <demo-wait-ancestors-static-section></demo-wait-ancestors-static-section>
109
111
  </template>
110
112
  </sonic-code>
111
113
 
@@ -113,7 +115,8 @@ When the parent is defined at load and already in the DOM, the child initializes
113
115
 
114
116
  <sonic-code>
115
117
  <template>
116
- <demo-wait-ancestors-ready-section></demo-wait-ancestors-ready-section>
118
+ <docs-demo-sources for="demo-wait-ancestors-ready-section"></docs-demo-sources>
119
+ <demo-wait-ancestors-ready-section></demo-wait-ancestors-ready-section>
117
120
  </template>
118
121
  </sonic-code>
119
122
 
@@ -145,7 +148,7 @@ The `sonic-connected` event bubbles, so you can listen to it from anywhere:
145
148
  <sonic-code language="typescript">
146
149
  <template>
147
150
  import { CONNECTED } from "@supersoniks/concorde/decorators";
148
- //
151
+
149
152
  someConnectable.addEventListener(CONNECTED, (e) => {
150
153
  console.log("Component connected:", e.target);
151
154
  });
@@ -0,0 +1,91 @@
1
+ # sub()
2
+
3
+ Read-only Lit directive: displays the current **DataProvider** value and updates whenever it is assigned.
4
+
5
+ ## Import
6
+
7
+ <sonic-code language="typescript">
8
+ <template>
9
+ import { sub } from "@supersoniks/concorde/directives";
10
+ </template>
11
+ </sonic-code>
12
+
13
+ ## String path
14
+
15
+ <sonic-code language="typescript">
16
+ <template>
17
+ render() {
18
+ return html`&lt;p&gt;Count: ${sub("myCounter.count")}&lt;/p&gt;`;
19
+ }
20
+ </template>
21
+ </sonic-code>
22
+
23
+ ## DataProviderKey (static)
24
+
25
+ <sonic-code language="typescript">
26
+ <template>
27
+ import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
28
+ import { sub } from "@supersoniks/concorde/directives";
29
+
30
+ const counterKey = new DataProviderKey&lt;{ count: number }&gt;("myCounter");
31
+
32
+ render() {
33
+ return html`&lt;p&gt;${sub(counterKey.count)}&lt;/p&gt;`;
34
+ }
35
+ </template>
36
+ </sonic-code>
37
+
38
+ ## Dynamic DataProviderKey (`${prop}`)
39
+
40
+ Like `@subscribe`: the path is resolved on the template **host component**; the directive re-subscribes when observed props change.
41
+
42
+ <sonic-code language="typescript">
43
+ <template>
44
+ import { DataProviderKey } from "@supersoniks/concorde/dataProviderKey";
45
+ import { sub } from "@supersoniks/concorde/directives";
46
+
47
+ type User = { firstName: string; lastName: string; email: string };
48
+ const userKey = new DataProviderKey&lt;User, { userIndex: number }&gt;(
49
+ "demoUsers.${userIndex}",
50
+ );
51
+
52
+ @customElement("demo-sub-dynamic")
53
+ export class DemoSubDynamic extends LitElement {
54
+ @property({ type: Number }) userIndex = 0;
55
+
56
+ render() {
57
+ return html`
58
+ &lt;p&gt;${sub(userKey.email)}&lt;/p&gt;
59
+ `;
60
+ }
61
+ }
62
+ </template>
63
+ </sonic-code>
64
+
65
+ ## Demo
66
+
67
+ <sonic-code toggleCode>
68
+ <template>
69
+ <docs-demo-sources for="demo-sub-template"></docs-demo-sources>
70
+ <demo-sub-template></demo-sub-template>
71
+ </template>
72
+ </sonic-code>
73
+
74
+ ## Concatenation (forms)
75
+
76
+ <sonic-code language="typescript">
77
+ <template>
78
+ html`&lt;span&gt;${sub(this.formDataProvider + ".email")}&lt;/span&gt;`
79
+ </template>
80
+ </sonic-code>
81
+
82
+ ## sub() vs get() / set() / dp()
83
+
84
+ | API | Context | Dynamic `${…}` |
85
+ |-----|----------|------------------|
86
+ | `sub()` | Lit template, reactive | Yes |
87
+ | `get` / `set` / `dp` | Imperative code | No |
88
+
89
+ Do not replace `sub(path)` with `get(path)` in a template: `get` returns a one-time snapshot.
90
+
91
+ See [DataProviderKey](#docs/_misc/dataProviderKey.md/dataProviderKey), [@subscribe](#docs/_decorators/subscribe.md/subscribe), and the `concorde-get-set-dp` migration skill.
@@ -0,0 +1,56 @@
1
+ # AI agents (skills & AGENTS.md)
2
+
3
+ Concorde ships **skills**, **rules**, and a root **`AGENTS.md`** so Cursor, JetBrains AI Assistant, and other coding agents follow framework patterns (DataProvider, decorators, short imports, scope, theme, migrations).
4
+
5
+ On a [starter](#docs/_getting-started/concorde-outside.md/concorde-outside) project, run `yarn ai:sync` after install. On a [manual / brownfield](#docs/_getting-started/concorde-manual-install.md/concorde-manual-install) project, install Concorde first, then use `ai-init` below.
6
+
7
+ ## Dedicated commands
8
+
9
+ | Context | Command |
10
+ |---------|---------|
11
+ | [create-concorde-ts-starter](https://www.npmjs.com/package/@supersoniks/create-concorde-ts-starter) project | `yarn ai:sync` |
12
+ | Any consumer app (after `yarn add @supersoniks/concorde`) | `node node_modules/@supersoniks/concorde/scripts/ai-init.mjs` |
13
+ | Concorde repo (contributors) | `yarn ai-init` |
14
+
15
+ <sonic-code language="shell">
16
+ <template>
17
+ # Starter (from project root)
18
+ yarn ai:sync
19
+
20
+ # Manual / existing project
21
+ node node_modules/@supersoniks/concorde/scripts/ai-init.mjs
22
+ </template>
23
+ </sonic-code>
24
+
25
+ ## Useful flags
26
+
27
+ | Flag | Effect |
28
+ |------|--------|
29
+ | `--merge-agents` | Append to an existing `AGENTS.md` instead of replacing it |
30
+ | `--overlay <dir>` | Copy extra rules/skills from another layer (e.g. starter kit) |
31
+ | `--cursor-only` | Install only `.cursor/skills` and `.cursor/rules` |
32
+ | `--jetbrains-only` | Install only `.aiassistant/rules/` |
33
+
34
+ ## What gets installed
35
+
36
+ | Path | Role |
37
+ |------|------|
38
+ | `AGENTS.md` | Project-level guide for agents (Concorde conventions) |
39
+ | `.cursor/skills/concorde/` | Core Lit + DataProvider patterns |
40
+ | `.cursor/skills/concorde-imports/` | Short `@supersoniks/concorde/…` import paths |
41
+ | `.cursor/skills/concorde-scope/` | `serviceURL`, `formDataProvider`, API configuration |
42
+ | `.cursor/skills/concorde-theme/` | `sonic-theme` and `--sc-*` tokens |
43
+ | `.cursor/skills/concorde-menu/` | `sonic-menu` navigation |
44
+ | `.cursor/skills/concorde-get-set-dp/` | `get` / `set` / `dp` and `DataProviderKey` migration |
45
+ | `.cursor/rules/concorde.mdc` | Cursor rules (always-on patterns) |
46
+ | `.aiassistant/rules/concorde.md` | JetBrains AI Assistant rules |
47
+
48
+ ## Workflow
49
+
50
+ 1. **Starter project:** [Installation](#docs/_getting-started/concorde-outside.md/concorde-outside) — `yarn ai:sync` is wired in the kit.
51
+ 2. **Existing / manual Vite project:** [Manual installation](#docs/_getting-started/concorde-manual-install.md/concorde-manual-install), then **`ai-init`** after `yarn add @supersoniks/concorde`.
52
+ 3. Commit `AGENTS.md`, `.cursor/`, and `.aiassistant/` if your team shares agent settings.
53
+ 4. Re-run the same command when you bump `@supersoniks/concorde` to refresh skills from `node_modules/@supersoniks/concorde/ai/`.
54
+ 5. In Cursor, skills under `.cursor/skills/` are picked up automatically; mention a skill in chat for focused tasks (e.g. migration → `concorde-get-set-dp`).
55
+
56
+ Source files in the npm package: `node_modules/@supersoniks/concorde/ai/` (see also `ai/README.md` in the [Concorde repository](https://github.com/supersoniks/concorde)).