@supersoniks/concorde 5.1.2 → 5.1.3-immutable.2

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 (497) hide show
  1. package/.agents/skills/concorde-docs/SKILL.md +1 -1
  2. package/.claude/skills +1 -0
  3. package/.github/workflows/ci.yml +21 -0
  4. package/.github-export-ignore +30 -0
  5. package/CONTRIBUTING.md +59 -0
  6. package/DEV.md +54 -0
  7. package/LICENSE +21 -0
  8. package/OPEN_SOURCE_CHECKPOINT.md +133 -0
  9. package/README.md +1 -1
  10. package/ai/README.md +29 -41
  11. package/ai/skills/concorde/SKILL.md +54 -324
  12. package/ai/skills/concorde/rules/composition.md +32 -0
  13. package/ai/skills/concorde/rules/data.md +47 -0
  14. package/ai/skills/concorde/rules/http.md +25 -0
  15. package/ai/skills/concorde/rules/lists-forms.md +35 -0
  16. package/ai/skills/concorde/rules/styling.md +46 -0
  17. package/ai/skills/concorde/rules/troubleshooting.md +45 -0
  18. package/build-infos.json +1 -1
  19. package/concorde-core.bundle.js +177 -177
  20. package/concorde-core.es.js +5183 -5018
  21. package/custom-elements-manifest.public.config.mjs +8 -0
  22. package/{.tmp-cem/custom-elements.json → custom-elements.json} +11722 -8868
  23. package/dist/concorde-core.bundle.js +177 -177
  24. package/dist/concorde-core.es.js +5183 -5018
  25. package/dist/docs-mock-api-sw.js +58 -223
  26. package/dist/docs-mock-api-sw.js.map +4 -4
  27. package/dist/img/og-concorde.jpg +0 -0
  28. package/dist/robots.txt +11 -10
  29. package/docs/assets/index-CD4WyL6o.css +1 -0
  30. package/docs/assets/index-D_alEsng.js +12122 -0
  31. package/docs/crawl/core/components/functional/date/date.html +190 -0
  32. package/docs/crawl/core/components/functional/fetch/fetch.html +109 -0
  33. package/docs/crawl/core/components/functional/if/if.html +27 -0
  34. package/docs/crawl/core/components/functional/jsonata/jsonata.html +79 -0
  35. package/docs/crawl/core/components/functional/list/list.html +70 -0
  36. package/docs/crawl/core/components/functional/mix/mix.html +45 -0
  37. package/docs/crawl/core/components/functional/queue/queue.html +96 -0
  38. package/docs/crawl/core/components/functional/router/router.html +106 -0
  39. package/docs/{core → crawl/core}/components/functional/sdui/sdui-extension-points.html +8 -9
  40. package/docs/crawl/core/components/functional/sdui/sdui.html +368 -0
  41. package/docs/crawl/core/components/functional/states/states.html +66 -0
  42. package/docs/crawl/core/components/functional/submit/submit.html +160 -0
  43. package/docs/{core → crawl/core}/components/functional/subscriber/subscriber.html +5 -6
  44. package/docs/crawl/core/components/functional/value/value.html +51 -0
  45. package/docs/{core → crawl/core}/components/ui/alert/alert.html +63 -53
  46. package/docs/{core → crawl/core}/components/ui/alert-messages/alert-messages.html +5 -6
  47. package/docs/{core → crawl/core}/components/ui/badge/badge.html +33 -33
  48. package/docs/crawl/core/components/ui/button/button.html +150 -0
  49. package/docs/crawl/core/components/ui/captcha/captcha.html +112 -0
  50. package/docs/{core → crawl/core}/components/ui/card/card.html +11 -12
  51. package/docs/{core → crawl/core}/components/ui/divider/divider.html +14 -14
  52. package/docs/crawl/core/components/ui/form/checkbox/checkbox.html +73 -0
  53. package/docs/crawl/core/components/ui/form/fieldset/fieldset.html +266 -0
  54. package/docs/{core → crawl/core}/components/ui/form/form-actions/form-actions.html +23 -23
  55. package/docs/crawl/core/components/ui/form/form-layout/form-layout.html +121 -0
  56. package/docs/crawl/core/components/ui/form/input/input.html +109 -0
  57. package/docs/crawl/core/components/ui/form/input-autocomplete/input-autocomplete.html +38 -0
  58. package/docs/crawl/core/components/ui/form/radio/radio.html +56 -0
  59. package/docs/crawl/core/components/ui/form/select/select.html +73 -0
  60. package/docs/crawl/core/components/ui/form/switch/switch.html +56 -0
  61. package/docs/crawl/core/components/ui/form/textarea/textarea.html +59 -0
  62. package/docs/crawl/core/components/ui/group/group.html +104 -0
  63. package/docs/{core → crawl/core}/components/ui/icon/icon.html +9 -10
  64. package/docs/crawl/core/components/ui/image/image.html +298 -0
  65. package/docs/{core → crawl/core}/components/ui/link/link.html +11 -12
  66. package/docs/crawl/core/components/ui/loader/loader.html +124 -0
  67. package/docs/{core → crawl/core}/components/ui/menu/menu.html +33 -34
  68. package/docs/crawl/core/components/ui/modal/modal.html +342 -0
  69. package/docs/crawl/core/components/ui/pop/pop.html +296 -0
  70. package/docs/{core → crawl/core}/components/ui/progress/progress.html +7 -8
  71. package/docs/crawl/core/components/ui/table/table.html +545 -0
  72. package/docs/crawl/core/components/ui/theme/theme.html +131 -0
  73. package/docs/crawl/core/components/ui/toast/toast.html +233 -0
  74. package/docs/crawl/core/components/ui/tooltip/tooltip.html +140 -0
  75. package/docs/{docs/getting-started → crawl/docs/_core-concept}/dataFlow.html +29 -26
  76. package/docs/crawl/docs/_core-concept/overview.html +66 -0
  77. package/docs/crawl/docs/_core-concept/subscriber.html +75 -0
  78. package/docs/crawl/docs/_decorators/ancestor-attribute.html +117 -0
  79. package/docs/crawl/docs/_decorators/auto-subscribe.html +202 -0
  80. package/docs/crawl/docs/_decorators/bind.html +141 -0
  81. package/docs/crawl/docs/_decorators/get.html +149 -0
  82. package/docs/crawl/docs/_decorators/handle.html +165 -0
  83. package/docs/crawl/docs/_decorators/on-assign.html +355 -0
  84. package/docs/crawl/docs/_decorators/patch.html +78 -0
  85. package/docs/crawl/docs/_decorators/post.html +139 -0
  86. package/docs/crawl/docs/_decorators/publish.html +61 -0
  87. package/docs/crawl/docs/_decorators/put.html +78 -0
  88. package/docs/crawl/docs/_decorators/subscribe.html +109 -0
  89. package/docs/crawl/docs/_decorators/wait-for-ancestors.html +137 -0
  90. package/docs/crawl/docs/_directives/sub.html +93 -0
  91. package/docs/{docs/getting-started → crawl/docs/_getting-started}/ai-agents.html +11 -12
  92. package/docs/crawl/docs/_getting-started/concorde-manual-install.html +100 -0
  93. package/docs/crawl/docs/_getting-started/concorde-outside.html +58 -0
  94. package/docs/{docs/getting-started → crawl/docs/_getting-started}/create-a-component.html +14 -11
  95. package/docs/{docs/getting-started → crawl/docs/_getting-started}/my-first-component.html +22 -13
  96. package/docs/crawl/docs/_getting-started/my-first-subscriber.html +110 -0
  97. package/docs/crawl/docs/_getting-started/pubsub.html +62 -0
  98. package/docs/crawl/docs/_getting-started/start.html +76 -0
  99. package/docs/{docs/getting-started → crawl/docs/_getting-started}/theming.html +9 -10
  100. package/docs/{docs/misc → crawl/docs/_misc}/api-configuration.html +18 -18
  101. package/docs/{docs/misc → crawl/docs/_misc}/dataProviderKey.html +11 -12
  102. package/docs/{docs/misc → crawl/docs/_misc}/docs-mock-api.html +28 -25
  103. package/docs/{docs/misc → crawl/docs/_misc}/dynamic-path.html +15 -16
  104. package/docs/{docs/misc → crawl/docs/_misc}/endpoint.html +15 -16
  105. package/docs/crawl/docs/_misc/formelement-subscriber-removal.html +138 -0
  106. package/docs/{docs/misc → crawl/docs/_misc}/html-integration.html +15 -12
  107. package/docs/crawl/hubs/decorators.html +547 -0
  108. package/docs/crawl/hubs/form-components.html +381 -0
  109. package/docs/crawl/hubs/remote-data.html +452 -0
  110. package/docs/crawl/index.html +103 -0
  111. package/docs/docs-mock-api-sw.js +223 -58
  112. package/docs/docs-mock-api-sw.js.map +4 -4
  113. package/docs/index.html +68 -95
  114. package/docs/llms-full.txt +3610 -2231
  115. package/docs/llms.txt +86 -94
  116. package/docs/robots.txt +10 -11
  117. package/docs/sitemap.xml +84 -78
  118. package/docs/src/core/components/functional/date/date.md +231 -91
  119. package/docs/src/core/components/functional/fetch/fetch.md +83 -66
  120. package/docs/src/core/components/functional/if/if.md +3 -28
  121. package/docs/src/core/components/functional/jsonata/jsonata.md +37 -42
  122. package/docs/src/core/components/functional/list/list.md +43 -99
  123. package/docs/src/core/components/functional/mix/mix.md +26 -31
  124. package/docs/src/core/components/functional/queue/queue.md +61 -40
  125. package/docs/src/core/components/functional/router/router.md +65 -64
  126. package/docs/src/core/components/functional/sdui/component-catalog.gaps.json +1 -1
  127. package/docs/src/core/components/functional/sdui/component-catalog.json +1 -1
  128. package/docs/src/core/components/functional/sdui/example.json +86 -19
  129. package/docs/src/core/components/functional/sdui/sdui-extension-points.md +1 -1
  130. package/docs/src/core/components/functional/sdui/sdui.md +358 -187
  131. package/docs/src/core/components/functional/states/states.md +61 -86
  132. package/docs/src/core/components/functional/submit/submit.md +72 -64
  133. package/docs/src/core/components/functional/value/value.md +22 -34
  134. package/docs/src/core/components/ui/alert/alert.md +58 -48
  135. package/docs/src/core/components/ui/badge/badge.md +34 -27
  136. package/docs/src/core/components/ui/button/button.md +106 -139
  137. package/docs/src/core/components/ui/captcha/captcha.md +32 -17
  138. package/docs/src/core/components/ui/card/card.md +6 -6
  139. package/docs/src/core/components/ui/divider/divider.md +11 -8
  140. package/docs/src/core/components/ui/form/checkbox/checkbox.md +42 -90
  141. package/docs/src/core/components/ui/form/fieldset/fieldset.md +75 -31
  142. package/docs/src/core/components/ui/form/form-actions/form-actions.md +16 -14
  143. package/docs/src/core/components/ui/form/form-layout/form-layout.md +29 -19
  144. package/docs/src/core/components/ui/form/input/input.md +95 -95
  145. package/docs/src/core/components/ui/form/input-autocomplete/input-autocomplete.md +29 -0
  146. package/docs/src/core/components/ui/form/radio/radio.md +34 -55
  147. package/docs/src/core/components/ui/form/select/select.md +45 -60
  148. package/docs/src/core/components/ui/form/switch/switch.md +34 -57
  149. package/docs/src/core/components/ui/form/textarea/textarea.md +38 -55
  150. package/docs/src/core/components/ui/group/group.md +33 -35
  151. package/docs/src/core/components/ui/icon/icon.md +4 -4
  152. package/docs/src/core/components/ui/image/image.md +107 -41
  153. package/docs/src/core/components/ui/link/link.md +6 -6
  154. package/docs/src/core/components/ui/loader/loader.md +55 -30
  155. package/docs/src/core/components/ui/menu/menu.md +27 -27
  156. package/docs/src/core/components/ui/modal/modal.md +96 -72
  157. package/docs/src/core/components/ui/pop/pop.md +158 -80
  158. package/docs/src/core/components/ui/progress/progress.md +2 -2
  159. package/docs/src/core/components/ui/table/table.md +255 -91
  160. package/docs/src/core/components/ui/theme/theme.md +36 -11
  161. package/docs/src/core/components/ui/toast/toast.md +117 -48
  162. package/docs/src/core/components/ui/tooltip/tooltip.md +62 -27
  163. package/docs/{docs/getting-started → src/docs/_core-concept}/dataFlow.md +18 -17
  164. package/docs/src/docs/_core-concept/overview.md +57 -0
  165. package/docs/src/docs/_core-concept/subscriber.md +75 -0
  166. package/docs/src/docs/_decorators/ancestor-attribute.md +128 -0
  167. package/docs/src/docs/_decorators/auto-subscribe.md +202 -0
  168. package/docs/src/docs/_decorators/bind.md +167 -0
  169. package/docs/src/docs/_decorators/get.md +162 -0
  170. package/docs/src/docs/_decorators/handle.md +175 -0
  171. package/docs/src/docs/_decorators/on-assign.md +390 -0
  172. package/docs/src/docs/_decorators/patch.md +70 -0
  173. package/docs/src/docs/_decorators/post.md +146 -0
  174. package/docs/src/docs/_decorators/publish.md +55 -0
  175. package/docs/src/docs/_decorators/put.md +70 -0
  176. package/docs/src/docs/_decorators/subscribe.md +100 -0
  177. package/docs/src/docs/_decorators/wait-for-ancestors.md +163 -0
  178. package/docs/src/docs/_directives/sub.md +91 -0
  179. package/docs/src/docs/{getting-started → _getting-started}/ai-agents.md +3 -3
  180. package/docs/src/docs/_getting-started/concorde-manual-install.md +133 -0
  181. package/docs/src/docs/_getting-started/concorde-outside.md +33 -0
  182. package/docs/{docs/getting-started → src/docs/_getting-started}/create-a-component.md +6 -3
  183. package/docs/src/docs/{getting-started → _getting-started}/my-first-component.md +14 -5
  184. package/docs/src/docs/_getting-started/my-first-subscriber.md +120 -0
  185. package/docs/src/docs/_getting-started/pubsub.md +37 -0
  186. package/docs/src/docs/{getting-started → _getting-started}/start.md +13 -1
  187. package/docs/src/docs/{getting-started → _getting-started}/theming.md +2 -2
  188. package/docs/src/docs/{misc → _misc}/api-configuration.md +12 -10
  189. package/docs/src/docs/{misc → _misc}/dataProviderKey.md +4 -4
  190. package/docs/{docs/misc → src/docs/_misc}/docs-mock-api.md +13 -12
  191. package/docs/{docs/misc → src/docs/_misc}/dynamic-path.md +8 -8
  192. package/docs/{docs/misc → src/docs/_misc}/endpoint.md +8 -8
  193. package/docs/src/docs/_misc/formelement-subscriber-removal.md +115 -0
  194. package/docs/{docs/misc → src/docs/_misc}/html-integration.md +5 -4
  195. package/docs/src/docs/search/docs-search.json +4375 -2420
  196. package/docs/src/tsconfig.declarations.json +22 -0
  197. package/docs/src/tsconfig.emit.json +23 -0
  198. package/docs/src/tsconfig.json +2 -23
  199. package/gitlab/job_github_mirror.sh +39 -0
  200. package/index.html +89 -0
  201. package/package.json +22 -4
  202. package/public/img/og-concorde.jpg +0 -0
  203. package/scripts/ai-init.mjs +104 -379
  204. package/scripts/ai-init.test.mjs +81 -239
  205. package/scripts/docs-markdown-vite-plugin.ts +11 -5
  206. package/scripts/docs-public-url.mjs +6 -0
  207. package/scripts/docs.htaccess +7 -0
  208. package/scripts/generate-cem.mjs +244 -0
  209. package/scripts/generate-cem.test.mjs +122 -0
  210. package/scripts/generate-crawl-docs.mjs +29 -5
  211. package/scripts/generate-sdui-catalog.mjs +11 -10
  212. package/scripts/github-export.mjs +150 -0
  213. package/scripts/post-build-docs.js +3 -0
  214. package/scripts/post-build.mjs +0 -0
  215. package/scripts/pre-publish.mjs +2 -1
  216. package/scripts/site-pages.mjs +212 -0
  217. package/scripts/site-pages.test.mjs +116 -0
  218. package/site-pages/about.md +22 -0
  219. package/site-pages/privacy.md +32 -0
  220. package/src/core/components/functional/date/date.md +1 -1
  221. package/src/core/components/functional/example/example.ts +3 -1
  222. package/src/core/components/functional/if/if.ts +0 -0
  223. package/src/core/components/functional/list/list.demo.ts +0 -0
  224. package/src/core/components/functional/list/list.md +6 -2
  225. package/src/core/components/functional/list/list.ts +17 -4
  226. package/src/core/components/functional/mix/mix.md +0 -0
  227. package/src/core/components/functional/router/router.demo.ts +0 -0
  228. package/src/core/components/functional/router/router.ts +0 -0
  229. package/src/core/components/functional/sdui/SDUIDescriptorTransformer.ts +0 -0
  230. package/src/core/components/functional/sdui/component-catalog.gaps.json +1 -1
  231. package/src/core/components/functional/sdui/component-catalog.json +2 -2
  232. package/src/core/components/functional/sdui/example.json +0 -0
  233. package/src/core/components/functional/sdui/sdui-immutable.spec.ts +74 -0
  234. package/src/core/components/functional/sdui/sdui.ts +33 -8
  235. package/src/core/components/functional/sdui/types.ts +0 -0
  236. package/src/core/components/functional/states/states.ts +0 -0
  237. package/src/core/components/functional/submit/submit.spec.ts +60 -0
  238. package/src/core/components/functional/submit/submit.ts +6 -3
  239. package/src/core/components/functional/subscriber/subscriber.ts +0 -0
  240. package/src/core/components/functional/value/value.md +0 -0
  241. package/src/core/components/functional/value/value.ts +0 -0
  242. package/src/core/components/ui/_css/size.ts +0 -0
  243. package/src/core/components/ui/captcha/altchaStyles.ts +0 -0
  244. package/src/core/components/ui/card/card.md +1 -1
  245. package/src/core/components/ui/form/input/input.md +19 -1
  246. package/src/core/components/ui/form/input/input.ts +8 -2
  247. package/src/core/components/ui/form/radio/radio.md +14 -0
  248. package/src/core/components/ui/image/image.md +17 -17
  249. package/src/core/components/ui/modal/modal.md +2 -2
  250. package/src/core/decorators/subscriber/bind.ts +9 -14
  251. package/src/core/decorators/subscriber/subscribe.ts +15 -9
  252. package/src/core/decorators/subscriber/subscribe.type-test.ts +14 -5
  253. package/src/core/mixins/Subscriber.ts +0 -0
  254. package/src/core/utils/DataBindObserver.ts +0 -0
  255. package/src/core/utils/Objects.ts +0 -0
  256. package/src/core/utils/PublisherProxy.ts +166 -125
  257. package/src/core/utils/deepReadonly.spec.ts +66 -0
  258. package/src/core/utils/deepReadonly.ts +71 -0
  259. package/src/core/utils/immutableValue.spec.ts +58 -0
  260. package/src/core/utils/immutableValue.ts +116 -0
  261. package/src/core/utils/nestedDotForm.spec.ts +88 -0
  262. package/src/core/utils/publisherImmutable.spec.ts +127 -0
  263. package/src/core/utils/route.ts +0 -0
  264. package/src/core/utils/strictGet.spec.ts +61 -0
  265. package/src/core/utils/strictGet.ts +102 -0
  266. package/src/css.d.ts +19 -0
  267. package/src/docs/components/docs-demo-sources.ts +3 -3
  268. package/src/docs/decorators/bind.md +23 -24
  269. package/src/docs/docs-location.spec.ts +10 -0
  270. package/src/docs/docs-location.ts +10 -0
  271. package/src/docs/example/decorators-demo-bind-demos.ts +11 -28
  272. package/src/docs/example/docs-toggle-demos.ts +1 -1
  273. package/src/docs/example/users.ts +0 -0
  274. package/src/docs/getting-started/ai-agents.md +51 -51
  275. package/src/docs/getting-started/dataFlow.md +2 -2
  276. package/src/docs/home/home.ts +3 -1
  277. package/src/docs/misc/publisher-get-immutability-docs-crawl.json +902 -0
  278. package/src/docs/misc/publisher-get-immutability-docs-recheck.json +13 -0
  279. package/src/docs/misc/publisher-get-immutability-study.md +229 -0
  280. package/src/docs/navigation/navigation.ts +5 -1
  281. package/src/docs/search/docs-search.json +358 -58
  282. package/src/docs/search/markdown-renderer.ts +6 -1
  283. package/src/docs/tailwind/css/docs/content.css +4 -5
  284. package/src/tsconfig.declarations.json +22 -0
  285. package/src/tsconfig.emit.json +23 -0
  286. package/src/tsconfig.json +53 -5
  287. package/src/tsconfig.tsbuildinfo +1 -1
  288. package/.taste-skill/after/desktop-fold.jpg +0 -0
  289. package/.taste-skill/after/desktop-page-01.jpg +0 -0
  290. package/.taste-skill/after/desktop-page-02.jpg +0 -0
  291. package/.taste-skill/after/desktop-page-03.jpg +0 -0
  292. package/.taste-skill/after/mobile-fold.jpg +0 -0
  293. package/.taste-skill/after/mobile-page-01.jpg +0 -0
  294. package/.taste-skill/after/report.json +0 -4103
  295. package/.taste-skill/after/tablet-fold.jpg +0 -0
  296. package/.taste-skill/after/tablet-page-01.jpg +0 -0
  297. package/.taste-skill/after/tablet-page-02.jpg +0 -0
  298. package/.taste-skill/before/desktop-fold.jpg +0 -0
  299. package/.taste-skill/before/desktop-page-01.jpg +0 -0
  300. package/.taste-skill/before/desktop-page-02.jpg +0 -0
  301. package/.taste-skill/before/mobile-fold.jpg +0 -0
  302. package/.taste-skill/before/mobile-page-01.jpg +0 -0
  303. package/.taste-skill/before/report.json +0 -2556
  304. package/.taste-skill/before/tablet-fold.jpg +0 -0
  305. package/.taste-skill/before/tablet-page-01.jpg +0 -0
  306. package/.taste-skill/direction.json +0 -510
  307. package/.taste-skill/notes.md +0 -84
  308. package/.taste-skill/prod/desktop-fold.jpg +0 -0
  309. package/.taste-skill/prod/desktop-page-01.jpg +0 -0
  310. package/.taste-skill/prod/desktop-page-02.jpg +0 -0
  311. package/.taste-skill/prod/desktop-page-03.jpg +0 -0
  312. package/.taste-skill/prod/mobile-fold.jpg +0 -0
  313. package/.taste-skill/prod/mobile-page-01.jpg +0 -0
  314. package/.taste-skill/prod/mobile-page-02.jpg +0 -0
  315. package/.taste-skill/prod/report.json +0 -1973
  316. package/.taste-skill/prod/tablet-fold.jpg +0 -0
  317. package/.taste-skill/prod/tablet-page-01.jpg +0 -0
  318. package/.taste-skill/prod/tablet-page-02.jpg +0 -0
  319. package/AUDIT.md +0 -228
  320. package/PLAN-AMELIORATION.md +0 -220
  321. package/PLAN-DOCS-AGENTS.md +0 -80
  322. package/ai/AGENTS.md +0 -56
  323. package/ai/cursor/rules/concorde-menu.mdc +0 -15
  324. package/ai/cursor/rules/concorde-scope.mdc +0 -14
  325. package/ai/cursor/rules/concorde-theme.mdc +0 -13
  326. package/ai/cursor/rules/concorde.mdc +0 -66
  327. package/ai/jetbrains/rules/concorde.md +0 -53
  328. package/ai/skills/concorde-get-set-dp/SKILL.md +0 -194
  329. package/ai/skills/concorde-imports/SKILL.md +0 -78
  330. package/ai/skills/concorde-menu/SKILL.md +0 -74
  331. package/ai/skills/concorde-scope/SKILL.md +0 -70
  332. package/ai/skills/concorde-theme/SKILL.md +0 -57
  333. package/docs/.htaccess +0 -75
  334. package/docs/404.html +0 -15
  335. package/docs/404.md +0 -5
  336. package/docs/assets/index-BgG6FFPt.js +0 -11765
  337. package/docs/assets/index-x89XINSG.css +0 -1
  338. package/docs/core/components/functional/date/date.html +0 -211
  339. package/docs/core/components/functional/date/date.md +0 -150
  340. package/docs/core/components/functional/fetch/fetch.html +0 -180
  341. package/docs/core/components/functional/fetch/fetch.md +0 -108
  342. package/docs/core/components/functional/if/if.html +0 -65
  343. package/docs/core/components/functional/if/if.md +0 -34
  344. package/docs/core/components/functional/jsonata/jsonata.html +0 -110
  345. package/docs/core/components/functional/jsonata/jsonata.md +0 -61
  346. package/docs/core/components/functional/list/list.html +0 -198
  347. package/docs/core/components/functional/list/list.md +0 -121
  348. package/docs/core/components/functional/mix/mix.html +0 -78
  349. package/docs/core/components/functional/mix/mix.md +0 -46
  350. package/docs/core/components/functional/queue/queue.html +0 -155
  351. package/docs/core/components/functional/queue/queue.md +0 -51
  352. package/docs/core/components/functional/router/router.html +0 -154
  353. package/docs/core/components/functional/router/router.md +0 -93
  354. package/docs/core/components/functional/sdui/sdui-extension-points.md +0 -94
  355. package/docs/core/components/functional/sdui/sdui.html +0 -397
  356. package/docs/core/components/functional/sdui/sdui.md +0 -307
  357. package/docs/core/components/functional/states/states.html +0 -141
  358. package/docs/core/components/functional/states/states.md +0 -112
  359. package/docs/core/components/functional/submit/submit.html +0 -178
  360. package/docs/core/components/functional/submit/submit.md +0 -106
  361. package/docs/core/components/functional/subscriber/subscriber.md +0 -91
  362. package/docs/core/components/functional/translation/translation.html +0 -60
  363. package/docs/core/components/functional/translation/translation.md +0 -38
  364. package/docs/core/components/functional/value/value.html +0 -81
  365. package/docs/core/components/functional/value/value.md +0 -47
  366. package/docs/core/components/ui/alert/alert.md +0 -186
  367. package/docs/core/components/ui/alert-messages/alert-messages.md +0 -85
  368. package/docs/core/components/ui/badge/badge.md +0 -181
  369. package/docs/core/components/ui/button/button.html +0 -309
  370. package/docs/core/components/ui/button/button.md +0 -216
  371. package/docs/core/components/ui/captcha/captcha.html +0 -80
  372. package/docs/core/components/ui/captcha/captcha.md +0 -56
  373. package/docs/core/components/ui/card/card.md +0 -215
  374. package/docs/core/components/ui/divider/divider.md +0 -126
  375. package/docs/core/components/ui/form/checkbox/checkbox.html +0 -200
  376. package/docs/core/components/ui/form/checkbox/checkbox.md +0 -125
  377. package/docs/core/components/ui/form/fieldset/fieldset.html +0 -238
  378. package/docs/core/components/ui/form/fieldset/fieldset.md +0 -161
  379. package/docs/core/components/ui/form/form-actions/form-actions.md +0 -98
  380. package/docs/core/components/ui/form/form-layout/form-layout.html +0 -115
  381. package/docs/core/components/ui/form/form-layout/form-layout.md +0 -109
  382. package/docs/core/components/ui/form/input/input.html +0 -235
  383. package/docs/core/components/ui/form/input/input.md +0 -142
  384. package/docs/core/components/ui/form/radio/radio.html +0 -144
  385. package/docs/core/components/ui/form/radio/radio.md +0 -78
  386. package/docs/core/components/ui/form/select/select.html +0 -178
  387. package/docs/core/components/ui/form/select/select.md +0 -86
  388. package/docs/core/components/ui/form/switch/switch.html +0 -146
  389. package/docs/core/components/ui/form/switch/switch.md +0 -80
  390. package/docs/core/components/ui/form/textarea/textarea.html +0 -162
  391. package/docs/core/components/ui/form/textarea/textarea.md +0 -82
  392. package/docs/core/components/ui/group/group.html +0 -113
  393. package/docs/core/components/ui/group/group.md +0 -77
  394. package/docs/core/components/ui/icon/icon.md +0 -214
  395. package/docs/core/components/ui/image/image.html +0 -194
  396. package/docs/core/components/ui/image/image.md +0 -167
  397. package/docs/core/components/ui/link/link.md +0 -81
  398. package/docs/core/components/ui/loader/loader.html +0 -114
  399. package/docs/core/components/ui/loader/loader.md +0 -87
  400. package/docs/core/components/ui/menu/menu.md +0 -370
  401. package/docs/core/components/ui/modal/modal.html +0 -324
  402. package/docs/core/components/ui/modal/modal.md +0 -211
  403. package/docs/core/components/ui/pop/pop.html +0 -250
  404. package/docs/core/components/ui/pop/pop.md +0 -216
  405. package/docs/core/components/ui/progress/progress.md +0 -109
  406. package/docs/core/components/ui/table/table.html +0 -333
  407. package/docs/core/components/ui/table/table.md +0 -267
  408. package/docs/core/components/ui/theme/theme.html +0 -116
  409. package/docs/core/components/ui/theme/theme.md +0 -97
  410. package/docs/core/components/ui/toast/toast.html +0 -185
  411. package/docs/core/components/ui/toast/toast.md +0 -147
  412. package/docs/core/components/ui/tooltip/tooltip.html +0 -112
  413. package/docs/core/components/ui/tooltip/tooltip.md +0 -89
  414. package/docs/docs/decorators/ancestor-attribute.html +0 -71
  415. package/docs/docs/decorators/ancestor-attribute.md +0 -59
  416. package/docs/docs/decorators/auto-subscribe.html +0 -61
  417. package/docs/docs/decorators/auto-subscribe.md +0 -40
  418. package/docs/docs/decorators/bind.html +0 -86
  419. package/docs/docs/decorators/bind.md +0 -77
  420. package/docs/docs/decorators/get.html +0 -166
  421. package/docs/docs/decorators/get.md +0 -160
  422. package/docs/docs/decorators/handle.html +0 -139
  423. package/docs/docs/decorators/handle.md +0 -119
  424. package/docs/docs/decorators/on-assign.html +0 -72
  425. package/docs/docs/decorators/on-assign.md +0 -63
  426. package/docs/docs/decorators/patch.html +0 -76
  427. package/docs/docs/decorators/patch.md +0 -44
  428. package/docs/docs/decorators/post.html +0 -150
  429. package/docs/docs/decorators/post.md +0 -134
  430. package/docs/docs/decorators/publish.html +0 -74
  431. package/docs/docs/decorators/publish.md +0 -56
  432. package/docs/docs/decorators/put.html +0 -83
  433. package/docs/docs/decorators/put.md +0 -51
  434. package/docs/docs/decorators/subscribe.html +0 -91
  435. package/docs/docs/decorators/subscribe.md +0 -87
  436. package/docs/docs/decorators/wait-for-ancestors.html +0 -82
  437. package/docs/docs/decorators/wait-for-ancestors.md +0 -76
  438. package/docs/docs/directives/sub.html +0 -79
  439. package/docs/docs/directives/sub.md +0 -75
  440. package/docs/docs/directives/t.html +0 -91
  441. package/docs/docs/directives/t.md +0 -73
  442. package/docs/docs/getting-started/ai-agents.md +0 -76
  443. package/docs/docs/getting-started/components.html +0 -27
  444. package/docs/docs/getting-started/components.md +0 -5
  445. package/docs/docs/getting-started/installation.html +0 -69
  446. package/docs/docs/getting-started/installation.md +0 -36
  447. package/docs/docs/getting-started/my-first-component.md +0 -227
  448. package/docs/docs/getting-started/start.html +0 -47
  449. package/docs/docs/getting-started/start.md +0 -35
  450. package/docs/docs/getting-started/theming.md +0 -253
  451. package/docs/docs/index.html +0 -99
  452. package/docs/docs/misc/api-configuration.md +0 -79
  453. package/docs/docs/misc/dataProviderKey.md +0 -168
  454. package/docs/hubs/decorators.html +0 -443
  455. package/docs/hubs/form-components.html +0 -404
  456. package/docs/hubs/remote-data.html +0 -371
  457. package/docs/img/archive/concorde-001-2cv.webp +0 -0
  458. package/docs/img/archive/concorde-red-arrows-2002-poster.jpg +0 -0
  459. package/docs/img/archive/concorde-red-arrows-2002.webm +0 -0
  460. package/docs/img/archive/credits.json +0 -28
  461. package/docs/index.md +0 -22
  462. package/docs/src/core/components/functional/translation/translation.md +0 -38
  463. package/docs/src/docs/decorators/ancestor-attribute.md +0 -59
  464. package/docs/src/docs/decorators/auto-subscribe.md +0 -40
  465. package/docs/src/docs/decorators/bind.md +0 -77
  466. package/docs/src/docs/decorators/get.md +0 -160
  467. package/docs/src/docs/decorators/handle.md +0 -119
  468. package/docs/src/docs/decorators/on-assign.md +0 -63
  469. package/docs/src/docs/decorators/patch.md +0 -44
  470. package/docs/src/docs/decorators/post.md +0 -134
  471. package/docs/src/docs/decorators/publish.md +0 -56
  472. package/docs/src/docs/decorators/put.md +0 -51
  473. package/docs/src/docs/decorators/subscribe.md +0 -87
  474. package/docs/src/docs/decorators/wait-for-ancestors.md +0 -76
  475. package/docs/src/docs/directives/sub.md +0 -75
  476. package/docs/src/docs/directives/t.md +0 -73
  477. package/docs/src/docs/getting-started/components.md +0 -5
  478. package/docs/src/docs/getting-started/create-a-component.md +0 -136
  479. package/docs/src/docs/getting-started/dataFlow.md +0 -87
  480. package/docs/src/docs/getting-started/installation.md +0 -36
  481. package/docs/src/docs/misc/docs-mock-api.md +0 -62
  482. package/docs/src/docs/misc/dynamic-path.md +0 -71
  483. package/docs/src/docs/misc/endpoint.md +0 -45
  484. package/docs/src/docs/misc/html-integration.md +0 -14
  485. /package/{docs → dist}/img/concorde/plane_01.jpg +0 -0
  486. /package/{docs → dist}/img/concorde/plane_02.jpg +0 -0
  487. /package/{docs → dist}/img/concorde/plane_03.jpg +0 -0
  488. /package/{docs → dist}/img/concorde/plane_04.jpg +0 -0
  489. /package/{docs → dist}/img/concorde/plane_05.jpg +0 -0
  490. /package/{docs → dist}/img/concorde/plane_06.jpg +0 -0
  491. /package/{docs → dist}/img/concorde/plane_07.jpg +0 -0
  492. /package/{docs → dist}/img/concorde/plane_08.jpg +0 -0
  493. /package/{docs → dist}/img/concorde/plane_09.jpg +0 -0
  494. /package/{docs → dist}/img/concorde/plane_10.jpg +0 -0
  495. /package/{docs → dist}/img/concorde/plane_11.jpg +0 -0
  496. /package/{docs → dist}/img/concorde/plane_12.jpg +0 -0
  497. /package/{docs → dist}/img/concorde/plane_13.jpg +0 -0
@@ -0,0 +1,116 @@
1
+ import assert from "node:assert/strict";
2
+ import fs from "node:fs";
3
+ import os from "node:os";
4
+ import path from "path";
5
+ import test from "node:test";
6
+ import { STATIC_PAGES, homepageJsonLd } from "./site-pages.mjs";
7
+ import {
8
+ generateHomepageMarkdown,
9
+ generateHtaccess,
10
+ generateSitemap,
11
+ writeAgentSiteArtifacts,
12
+ } from "./generate-crawl-docs.mjs";
13
+
14
+ function stripTags(html) {
15
+ return html
16
+ .replace(/<[^>]+>/g, " ")
17
+ .replace(/\s+/g, " ")
18
+ .trim();
19
+ }
20
+
21
+ test("homepage JSON-LD has SoftwareApplication and complete Organization", () => {
22
+ const ld = homepageJsonLd();
23
+ assert.equal(ld["@context"], "https://schema.org");
24
+ const byType = Object.fromEntries(ld["@graph"].map((n) => [n["@type"], n]));
25
+ const app = byType.SoftwareApplication;
26
+ assert.ok(app.name && app.description && app.url && app.offers);
27
+ assert.equal(app.publisher["@id"], byType.Organization["@id"]);
28
+ const org = byType.Organization;
29
+ assert.equal(org.url, "https://supersoniks.com/");
30
+ assert.equal(org.address["@type"], "PostalAddress");
31
+ assert.equal(org.address.addressLocality, "Tours");
32
+ assert.equal(org.contactPoint["@type"], "ContactPoint");
33
+ assert.ok(org.contactPoint.contactType && org.contactPoint.telephone);
34
+ assert.ok(org.sameAs.every((u) => u.startsWith("https://")));
35
+ });
36
+
37
+ test("source index.html embeds the generated JSON-LD and Open Graph tags", () => {
38
+ const html = fs.readFileSync(path.resolve("index.html"), "utf8");
39
+ const block = html.match(
40
+ /<script type="application\/ld\+json">([\s\S]*?)<\/script>/,
41
+ );
42
+ assert.ok(block, "JSON-LD script missing");
43
+ assert.deepEqual(JSON.parse(block[1]), homepageJsonLd());
44
+ for (const prop of ["og:type", "og:title", "og:description", "og:url", "og:image"]) {
45
+ assert.match(html, new RegExp(`<meta property="${prop}" content="[^"]+"`), prop);
46
+ }
47
+ assert.match(
48
+ html,
49
+ /<meta property="og:image" content="https:\/\/concorde\.supersoniks\.org\/img\/og-concorde\.jpg"/,
50
+ );
51
+ assert.ok(fs.existsSync(path.resolve("public/img/og-concorde.jpg")));
52
+ assert.match(html, /<meta name="twitter:card" content="summary_large_image"/);
53
+ assert.match(html, /href="\/about"/);
54
+ assert.match(html, /href="\/privacy"/);
55
+ });
56
+
57
+ test("site page Markdown is read from site-pages/", () => {
58
+ assert.deepEqual(
59
+ STATIC_PAGES.map((p) => p.slug),
60
+ ["about", "privacy"],
61
+ );
62
+ for (const { slug, markdown } of STATIC_PAGES) {
63
+ assert.equal(
64
+ markdown,
65
+ fs.readFileSync(path.resolve(`site-pages/${slug}.md`), "utf8"),
66
+ );
67
+ assert.match(markdown, /^# /);
68
+ }
69
+ });
70
+
71
+ test("site pages are SPA shells with their own metadata and inline content", () => {
72
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "concorde-pages-"));
73
+ try {
74
+ writeAgentSiteArtifacts(dir);
75
+ for (const { slug } of STATIC_PAGES) {
76
+ const md = fs.readFileSync(path.join(dir, `${slug}.md`), "utf8");
77
+ const html = fs.readFileSync(path.join(dir, `${slug}.html`), "utf8");
78
+ assert.ok(md.length >= 500, `${slug}.md too short (${md.length})`);
79
+ const section = html.match(
80
+ /<section id="docs-agent-content"[^>]*>([\s\S]*?)<\/section>/,
81
+ );
82
+ assert.ok(section, `${slug}.html page section missing`);
83
+ assert.ok(stripTags(section[1]).length >= 500, `${slug}.html too short`);
84
+ assert.match(section[1], /<h1/);
85
+ assert.doesNotMatch(section[1], /<h1>Concorde by Supersoniks<\/h1>/);
86
+ assert.match(html, /<sonic-router>/, "SPA shell expected");
87
+ const url = `https://concorde\\.supersoniks\\.org/${slug}`;
88
+ assert.match(html, new RegExp(`<link rel="canonical" href="${url}" />`));
89
+ assert.match(html, new RegExp(`<meta property="og:url" content="${url}"`));
90
+ assert.match(html, new RegExp(`href="/${slug}\\.md"`));
91
+ assert.equal((html.match(/<title>/g) || []).length, 1);
92
+ const ld = JSON.parse(
93
+ html.match(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/)[1],
94
+ );
95
+ assert.equal(ld.url, `https://concorde.supersoniks.org/${slug}`);
96
+ assert.equal(ld.publisher.address.addressLocality, "Tours");
97
+ }
98
+ } finally {
99
+ fs.rmSync(dir, { recursive: true, force: true });
100
+ }
101
+ });
102
+
103
+ test("sitemap and homepage Markdown link the site pages", () => {
104
+ const xml = generateSitemap([], []);
105
+ const md = generateHomepageMarkdown();
106
+ for (const slug of ["about", "privacy"]) {
107
+ assert.match(xml, new RegExp(`<loc>https://concorde\\.supersoniks\\.org/${slug}</loc>`));
108
+ assert.match(md, new RegExp(`https://concorde\\.supersoniks\\.org/${slug}\\)`));
109
+ }
110
+ });
111
+
112
+ test("htaccess serves site pages as HTML or Markdown", () => {
113
+ const conf = generateHtaccess();
114
+ assert.match(conf, /\^\(about\|privacy\)\$ \/\$1\.md \[END\]/);
115
+ assert.match(conf, /\^\(about\|privacy\)\$ \/\$1\.html \[END\]/);
116
+ });
@@ -0,0 +1,22 @@
1
+ # About Concorde
2
+
3
+ Concorde is an open-source web components framework built on [Lit](https://lit.dev). It is designed, written and maintained by [Supersoniks](https://supersoniks.com/), a company based in Tours, France, which uses it in production.
4
+
5
+ ## Why it exists
6
+
7
+ Supersoniks works on many stacks: PHP back offices, static pages, React and Vue front ends. Rewriting the same buttons, forms and data lists for each stack was slow and led to inconsistent interfaces. Concorde solves this with standard custom elements (`sonic-*`) that work the same way in any host page, plus a small data layer (DataProvider, decorators) that connects components to each other and to REST APIs without a framework.
8
+
9
+ ## What it includes
10
+
11
+ - UI components: buttons, inputs, selects, checkboxes, modals, toasts, cards, menus, tooltips and more.
12
+ - Data components: lists, queues, fetch, submit, router, states and server-driven UI from JSON.
13
+ - Lit decorators such as `@get`, `@post`, `@subscribe` and `@bind`.
14
+ - Theming through CSS custom properties, with light and dark modes.
15
+
16
+ ## Licence and distribution
17
+
18
+ Concorde is published on npm as [`@supersoniks/concorde`](https://www.npmjs.com/package/@supersoniks/concorde) under the MIT licence. This site (https://concorde.supersoniks.org) is its official documentation.
19
+
20
+ ## Publisher
21
+
22
+ Supersoniks, 22 rue du Docteur Bretonneau, 37000 Tours, France · +33 2 47 68 93 25 · [contact Supersoniks](https://supersoniks.com/un-projet). See also the [privacy notice](/privacy).
@@ -0,0 +1,32 @@
1
+ # Privacy notice
2
+
3
+ This notice covers the Concorde documentation site, https://concorde.supersoniks.org, published by [Supersoniks](https://supersoniks.com/), 22 rue du Docteur Bretonneau, 37000 Tours, France.
4
+
5
+ ## What this site does not do
6
+
7
+ - It has no user accounts, sign-up or login.
8
+ - It sets no cookies and runs no analytics, advertising or tracking scripts.
9
+ - It has no forms that send personal data to Supersoniks.
10
+
11
+ ## What is stored in your browser
12
+
13
+ - `sonicTheme` in localStorage remembers your light or dark theme choice. It never leaves your browser.
14
+ - A service worker serves the mock API used by live examples (`/docs-mock-api/…`). It runs locally in your browser.
15
+ - Some examples demonstrate features that store values in localStorage (for example dismissed alerts or cached labels). They only run when you open those pages.
16
+
17
+ ## Third-party services
18
+
19
+ To display pages, your browser loads files from these providers, which receive your IP address and browser details as part of any web request:
20
+
21
+ - Google Fonts (fonts.googleapis.com, fonts.gstatic.com) for the Schibsted Grotesk font.
22
+ - jsDelivr (cdn.jsdelivr.net) for the search and Markdown libraries.
23
+ - cdn.supersoniks.com for icons.
24
+ - A few live examples call public demo APIs (geo.api.gouv.fr, reqres.in, i.pravatar.cc) when you open them.
25
+
26
+ ## Server logs
27
+
28
+ Like any web server, the host records technical request data (IP address, URL, date, user agent) to operate and secure the site.
29
+
30
+ ## Your rights
31
+
32
+ Under the GDPR you can ask for access to, correction or deletion of data about you. Contact Supersoniks through [supersoniks.com/un-projet](https://supersoniks.com/un-projet), by phone at +33 2 47 68 93 25, or by mail at 22 rue du Docteur Bretonneau, 37000 Tours, France. See also the [Supersoniks privacy policy](https://supersoniks.com/politique-de-confidentialite) and [legal notice](https://supersoniks.com/mentions-legales).
@@ -116,7 +116,7 @@ A child `<template data-value="<part>">` wraps a part in your own markup: `weekd
116
116
  <template data-value="year"><span class="block text-lg font-bold leading-tight"></span></template>
117
117
  <template data-value="to"><span class="block my-1 text-3xl leading-none">↓</span></template>
118
118
  </sonic-date>
119
- <sonic-image src="img/concorde/plane_03.jpg" alt="Concorde specifications board" ratio="1/1" cover></sonic-image>
119
+ <sonic-image src="/img/concorde/plane_03.jpg" alt="Concorde specifications board" ratio="1/1" cover></sonic-image>
120
120
  </div>
121
121
  </template>
122
122
  </sonic-code>
@@ -2,6 +2,7 @@ import { html, LitElement } from "lit";
2
2
  import { customElement, property, state } from "lit/decorators.js";
3
3
  import { Endpoint } from "@supersoniks/concorde/utils";
4
4
  import { DataProviderKey } from "@supersoniks/concorde/core/utils/dataProviderKey";
5
+ import type { Frozen } from "@supersoniks/concorde/utils/deepReadonly";
5
6
  import {
6
7
  get,
7
8
  publish,
@@ -19,6 +20,7 @@ const endpoint = new Endpoint<City[], { limit: number }>(
19
20
  /** Même path que l’endpoint ; type = charge utile `@get` pour `@publish`. */
20
21
  const dpKey = new DataProviderKey<ApiResult<City[]>>(endpoint.path);
21
22
 
23
+
22
24
  const tagName = "sonic-example"; // For Astro.build
23
25
  @customElement(tagName)
24
26
  export class SonicComponent extends LitElement {
@@ -31,7 +33,7 @@ export class SonicComponent extends LitElement {
31
33
 
32
34
  @state()
33
35
  @subscribe(dpKey.result)
34
- geoCommunesResult?: City[];
36
+ geoCommunesResult?: Frozen<City[]>;
35
37
 
36
38
  render() {
37
39
  return html` <span part="api-get-demo">
File without changes
File without changes
@@ -1,6 +1,6 @@
1
1
  # List
2
2
 
3
- **`sonic-list`** renders one row per entry of its `props` array, either set directly or loaded with `fetch`. From Lit, pass a row renderer on the `items` property; in plain HTML, declare `<template>` children. Each row gets its own data provider at `<dataProvider>/list-item/<index>`.
3
+ **`sonic-list`** renders one row per entry of its `props` array, either set directly or loaded with `fetch`. In TypeScript, prefer the `.items` row renderer (with `.separator` · `.noItems` · `.skeleton`); `<template>` children are for plain HTML hosts without Lit. Each row gets its own data provider at `<dataProvider>/list-item/<index>`.
4
4
 
5
5
  ## Basic example
6
6
 
@@ -16,7 +16,7 @@ Add `fetch` to load `props` from `serviceURL` + `endPoint` (or `dataProvider` wh
16
16
  </template>
17
17
  </sonic-code>
18
18
 
19
- Fetch attributes (`lazyload` · `refetchEveryMs` · headers · authentication) are shared with [Fetch](/core/components/functional/fetch).
19
+ This example uses HTML templates; from Lit, use the [row renderer](#row-renderer) instead. Fetch attributes (`lazyload` · `refetchEveryMs` · headers · authentication) are shared with [Fetch](/core/components/functional/fetch).
20
20
 
21
21
  ## Row renderer
22
22
 
@@ -98,6 +98,8 @@ With no `loader`, nothing is shown while fetching. Set `loader` (`fixed`) or `lo
98
98
  | `loader` | `fixed` (when empty) · `inline` | Loader shown while fetching. None by default |
99
99
  | `debug` | boolean | Shows each row's data on hover |
100
100
 
101
+ Properties (TypeScript / Lit, preferred):
102
+
101
103
  | Property | Type | Description |
102
104
  |----------|------|-------------|
103
105
  | `props` | array · object | Rows to render |
@@ -107,6 +109,8 @@ With no `loader`, nothing is shown while fetching. Set `loader` (`fixed`) or `lo
107
109
  | `skeleton` | `() => TemplateResult` | Rendered while fetching |
108
110
  | `itemPropertyMap` | object | `propertyMap` passed to each row subscriber |
109
111
 
112
+ Templates (plain HTML hosts):
113
+
110
114
  | Template | Description |
111
115
  |----------|-------------|
112
116
  | `<template>` | Row markup; several are used in turn |
@@ -339,10 +339,23 @@ export class List extends Fetcher(Subscriber(TemplatesContainer(LitElement))) {
339
339
  };
340
340
 
341
341
  childPublisher._key_ = key + "";
342
- childPublisher._metadata_ = {
343
- ...childPublisher._metadata_.get(),
344
- ...metadata,
345
- };
342
+ // Immutable store: assigning `_metadata_` every render rebuilds the item + list
343
+ // publisher and retriggers render → infinite loop. Only write when values change.
344
+ const previousMetadata =
345
+ childPublisher._metadata_?.get() as ListItemMetadata | null;
346
+ if (
347
+ previousMetadata?.key !== metadata.key ||
348
+ previousMetadata?.even !== metadata.even ||
349
+ previousMetadata?.odd !== metadata.odd ||
350
+ previousMetadata?.onlyChild !== metadata.onlyChild ||
351
+ previousMetadata?.firstChild !== metadata.firstChild ||
352
+ previousMetadata?.lastChild !== metadata.lastChild
353
+ ) {
354
+ childPublisher._metadata_ = {
355
+ ...(previousMetadata || {}),
356
+ ...metadata,
357
+ };
358
+ }
346
359
 
347
360
  // Essayer d'utiliser les templates programmatiques
348
361
  const programmaticTemplate = this.handleProgrammaticTemplates(
File without changes
File without changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "generatedAt": "2026-10-26T08:58:38.625Z",
3
+ "generatedAt": "2026-11-02T09:56:04.637Z",
4
4
  "count": 66,
5
5
  "items": [
6
6
  {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 1,
3
- "generatedAt": "2026-10-26T08:58:38.625Z",
3
+ "generatedAt": "2026-11-02T09:56:04.637Z",
4
4
  "componentCount": 67,
5
5
  "components": [
6
6
  {
@@ -755,7 +755,7 @@
755
755
  {
756
756
  "name": "geoCommunesResult",
757
757
  "attribute": "geoCommunesResult",
758
- "type": "City[] | undefined"
758
+ "type": "Frozen<City[]> | undefined"
759
759
  }
760
760
  ]
761
761
  },
File without changes
@@ -0,0 +1,74 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import TestUtils from "@supersoniks/concorde/test-utils/TestUtils";
3
+ import { PublisherManager } from "@supersoniks/concorde/core/utils/PublisherProxy";
4
+ import { snapshotLightDom } from "./sdui-dom-snapshot";
5
+ import type { SDUIDescriptor } from "./types";
6
+ import "./sdui";
7
+ import type { SonicSDUI } from "./sdui";
8
+
9
+ async function flushSdui(elt: SonicSDUI) {
10
+ await elt.updateComplete;
11
+ await Promise.resolve();
12
+ await Promise.resolve();
13
+ await elt.updateComplete;
14
+ }
15
+
16
+ afterEach(() => {
17
+ document.body.innerHTML = "";
18
+ PublisherManager.getInstance().delete("sdui-immutable-spec");
19
+ });
20
+
21
+ describe("sonic-sdui + immutable publisher props", () => {
22
+ it("rend un descripteur servi par un publisher frozen (cas doc / dataProvider)", async () => {
23
+ const descriptor: SDUIDescriptor = {
24
+ nodes: [
25
+ {
26
+ tagName: "div",
27
+ attributes: { class: "sdui-imm-ok" },
28
+ textContent: "FROM_FROZEN_PROPS",
29
+ },
30
+ ],
31
+ };
32
+
33
+ const [elt] = TestUtils.bootstrap(
34
+ `<sonic-sdui dataProvider="sdui-immutable-spec"></sonic-sdui>`
35
+ ) as unknown as SonicSDUI[];
36
+
37
+ // Même chemin que Subscriber : props → publisher.set → get() frozen → willUpdate
38
+ elt.props = descriptor as never;
39
+ await flushSdui(elt);
40
+
41
+ expect(Object.isFrozen(elt.props)).toBe(true);
42
+ expect(elt.querySelector(".sdui-imm-ok")?.textContent).toBe(
43
+ "FROM_FROZEN_PROPS"
44
+ );
45
+ expect(JSON.stringify(snapshotLightDom(elt))).toContain("FROM_FROZEN_PROPS");
46
+ });
47
+
48
+ it("supporte libraryKey avec props frozen (merge library mutable en local)", async () => {
49
+ const descriptor: SDUIDescriptor = {
50
+ library: {
51
+ card: {
52
+ tagName: "div",
53
+ attributes: { class: "lib-card" },
54
+ nodes: [{ tagName: "span" }],
55
+ contentElementSelector: "span",
56
+ },
57
+ },
58
+ nodes: [
59
+ {
60
+ libraryKey: "card",
61
+ nodes: [{ tagName: "em", textContent: "LIB_OK" }],
62
+ },
63
+ ],
64
+ };
65
+
66
+ const [elt] = TestUtils.bootstrap(
67
+ `<sonic-sdui dataProvider="sdui-immutable-spec"></sonic-sdui>`
68
+ ) as unknown as SonicSDUI[];
69
+ elt.props = descriptor as never;
70
+ await flushSdui(elt);
71
+
72
+ expect(elt.querySelector(".lib-card em")?.textContent).toBe("LIB_OK");
73
+ });
74
+ });
@@ -69,6 +69,9 @@ export class SonicSDUI extends Fetcher(Subscriber(LitElement)) {
69
69
  */
70
70
  @property({ type: String }) opsDataProvider?: string;
71
71
  private sduiDescriptor: SDUIDescriptor = {};
72
+ /** Ref publisher/props d’origine (peut être frozen) — pour skip willUpdate. */
73
+ private sduiPropsRef: SDUIDescriptor | Record<string, SDUIDescriptor> | null =
74
+ null;
72
75
  private nodeIndex = new Map<string, SDUINode>();
73
76
  /** Garde-fou cycles childNodeIds. */
74
77
  private renderingStack = new Set<string>();
@@ -78,6 +81,16 @@ export class SonicSDUI extends Fetcher(Subscriber(LitElement)) {
78
81
  private incrementalActive = false;
79
82
  private opsUnsub?: () => void;
80
83
 
84
+ /** Copie de travail mutable : updateContents / parseRootNodes mutent library & nodes. */
85
+ private cloneDescriptor(source: SDUIDescriptor | null | undefined): SDUIDescriptor {
86
+ if (source == null) return {};
87
+ try {
88
+ return structuredClone(source) as SDUIDescriptor;
89
+ } catch {
90
+ return JSON.parse(JSON.stringify(source)) as SDUIDescriptor;
91
+ }
92
+ }
93
+
81
94
  private get isSafeProfile(): boolean {
82
95
  return this.profile === "safe";
83
96
  }
@@ -100,21 +113,33 @@ export class SonicSDUI extends Fetcher(Subscriber(LitElement)) {
100
113
  return;
101
114
  }
102
115
  if (this.props == null) {
103
- this.sduiDescriptor = {};
104
- }
105
- {
116
+ if (this.sduiPropsRef != null || Object.keys(this.sduiDescriptor).length) {
117
+ this.sduiPropsRef = null;
118
+ this.sduiDescriptor = {};
119
+ this.updateContents();
120
+ }
121
+ } else {
122
+ const propsRoot = this.props as
123
+ | SDUIDescriptor
124
+ | Record<string, SDUIDescriptor>;
106
125
  const newSduiDescriptor = this.sduiKey
107
- ? (this.props as Record<string, SDUIDescriptor>)[this.sduiKey]
108
- : (this.props as SDUIDescriptor);
126
+ ? (propsRoot as Record<string, SDUIDescriptor>)[this.sduiKey]
127
+ : (propsRoot as SDUIDescriptor);
109
128
  const profileChanged = changedProperties.has("profile");
110
129
  const partialChanged = changedProperties.has("partial");
130
+ // Comparer la ref d’entrée (frozen publisher), pas la copie locale mutable.
111
131
  if (
112
- this.sduiDescriptor == newSduiDescriptor &&
132
+ this.sduiPropsRef == newSduiDescriptor &&
113
133
  !profileChanged &&
114
134
  !partialChanged
115
- )
135
+ ) {
136
+ super.willUpdate(changedProperties);
116
137
  return;
117
- this.sduiDescriptor = newSduiDescriptor;
138
+ }
139
+ this.sduiPropsRef = newSduiDescriptor ?? null;
140
+ // Ownership locale : le store immutable freeze props ; SDUI doit muter
141
+ // library / nodes / toast pendant le rendu.
142
+ this.sduiDescriptor = this.cloneDescriptor(newSduiDescriptor);
118
143
  this.updateContents();
119
144
  }
120
145
  super.willUpdate(changedProperties);
File without changes
File without changes
@@ -0,0 +1,60 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import TestUtils from "@supersoniks/concorde/test-utils/TestUtils";
3
+ import { PublisherManager } from "@supersoniks/concorde/core/utils/PublisherProxy";
4
+ import "./submit";
5
+ import type { Submit } from "./submit";
6
+
7
+ afterEach(() => {
8
+ document.body.innerHTML = "";
9
+ vi.restoreAllMocks();
10
+ PublisherManager.getInstance().delete("submit-spec-form");
11
+ PublisherManager.getInstance().delete("submit-spec-result");
12
+ });
13
+
14
+ describe("sonic-submit + immutable publisher", () => {
15
+ it("envoie le formulaire sans muter le get() frozen (delete isFormValid)", async () => {
16
+ const [elt] = TestUtils.bootstrap(`
17
+ <div
18
+ formDataProvider="submit-spec-form"
19
+ submitResultDataProvider="submit-spec-result"
20
+ serviceURL="https://example.test"
21
+ dataProvider="api/register"
22
+ >
23
+ <sonic-submit></sonic-submit>
24
+ </div>
25
+ `) as unknown as [HTMLElement];
26
+
27
+ const submit = elt.querySelector("sonic-submit") as Submit;
28
+ await submit.updateComplete;
29
+
30
+ const formPub = PublisherManager.get("submit-spec-form");
31
+ formPub.set({
32
+ email: "ada@example.com",
33
+ password: "secret",
34
+ isFormValid: true,
35
+ needsCaptchaValidation: false,
36
+ });
37
+
38
+ const frozen = formPub.get() as Record<string, unknown>;
39
+ expect(Object.isFrozen(frozen)).toBe(true);
40
+
41
+ const post = vi.fn(async () => ({ id: 1, token: "t" }));
42
+ (submit as any).api = { post, lastResult: { ok: true } };
43
+
44
+ await submit.submit();
45
+
46
+ expect(post).toHaveBeenCalledTimes(1);
47
+ const call = post.mock.calls[0] as unknown as [
48
+ unknown,
49
+ Record<string, unknown>,
50
+ ];
51
+ const body = call[1];
52
+ expect(body).toMatchObject({
53
+ email: "ada@example.com",
54
+ password: "secret",
55
+ });
56
+ expect(body.isFormValid).toBeUndefined();
57
+ // Store still frozen and untouched for isFormValid key presence as set
58
+ expect(formPub.get().email).toBe("ada@example.com");
59
+ });
60
+ });
@@ -137,7 +137,8 @@ export class Submit extends Subscriber(LitElement) {
137
137
  const method = this.getAttribute("method")?.toLocaleLowerCase() || "post";
138
138
 
139
139
  const sendAsFormData = this.hasAttribute("sendAsFormData");
140
- const formData = formPublisher.get();
140
+ // Shallow copies: publisher get() is frozen under the immutable store.
141
+ const formData: Record<string, any> = { ...(formPublisher.get() as object) };
141
142
  delete formData.isFormValid;
142
143
  const headesDataProvider = this.getAncestorAttributeValue(
143
144
  "headersDataProvider"
@@ -145,8 +146,10 @@ export class Submit extends Subscriber(LitElement) {
145
146
  const headerPublisher = headesDataProvider
146
147
  ? PublisherManager.getInstance().get(headesDataProvider)
147
148
  : null;
148
- let headerData: any = {};
149
- if (headerPublisher) headerData = headerPublisher.get();
149
+ let headerData: Record<string, any> = {};
150
+ if (headerPublisher) {
151
+ headerData = { ...(headerPublisher.get() as object) };
152
+ }
150
153
  let result: SubmitResult = null;
151
154
  const dataProvider = this.getAncestorAttributeValue("dataProvider");
152
155
  const endPoint = this.endPoint || dataProvider;
File without changes
File without changes
File without changes
File without changes
@@ -160,7 +160,7 @@ Header, media, body, and footer together.
160
160
  <sonic-image
161
161
  rounded
162
162
  ratio="16/9"
163
- src="img/concorde/plane_04.jpg"
163
+ src="/img/concorde/plane_04.jpg"
164
164
  alt="Concorde on the apron under a blue sky"
165
165
  ></sonic-image>
166
166
  <p class="mb-0">
@@ -89,7 +89,25 @@ Fields sharing the same `formDataProvider` and `name` stay in sync. The attribut
89
89
  </template>
90
90
  </sonic-code>
91
91
 
92
- `sameValueAs` sets `pattern` to the value of another field in the same store, for confirmation fields.
92
+ ## Nested keys
93
+
94
+ A dot in `name` stores a nested object in the form publisher.
95
+
96
+ <sonic-code>
97
+ <template>
98
+ <div formDataProvider="input-nested-demo" class="grid grid-cols-2 gap-3">
99
+ <sonic-input label="Passenger" name="passenger.name" value="Ada Lindqvist"></sonic-input>
100
+ <sonic-input label="Seat" name="passenger.seat" value="2A"></sonic-input>
101
+ <div class="col-span-2">
102
+ <sonic-value dataProvider="input-nested-demo" key="passenger.name"></sonic-value>
103
+ ·
104
+ <sonic-value dataProvider="input-nested-demo" key="passenger.seat"></sonic-value>
105
+ </div>
106
+ </div>
107
+ </template>
108
+ </sonic-code>
109
+
110
+ `sameValueAs` sets `pattern` to the value of another field in the same store, for confirmation fields. Dot paths are supported.
93
111
 
94
112
  <sonic-code>
95
113
  <template>
@@ -15,6 +15,7 @@ import {
15
15
  } from "@supersoniks/concorde/core/components/ui/form/css/form-control";
16
16
  import FormElement from "@supersoniks/concorde/core/mixins/FormElement";
17
17
  import FormInput from "@supersoniks/concorde/core/mixins/FormInput";
18
+ import { traverseDotNotation } from "@supersoniks/concorde/core/utils/Objects";
18
19
  import { classMap } from "lit/directives/class-map.js";
19
20
  import {
20
21
  fontSize,
@@ -107,7 +108,10 @@ export class Input extends FormInput(FormElement(LitElement)) {
107
108
  (this.pattern = this.escapeRegExp(v));
108
109
  const formPublisher = this.getFormPublisher();
109
110
  if (!formPublisher || !this.sameValueAsName) return;
110
- formPublisher[this.sameValueAsName].onAssign(this.sameValueAsHandle);
111
+ // Dot notation (same as FormElement name) — not formPublisher["a.b"].
112
+ traverseDotNotation(formPublisher, this.sameValueAsName).onAssign(
113
+ this.sameValueAsHandle,
114
+ );
111
115
  }
112
116
  if (this.type == "password") {
113
117
  this.isPassword = true;
@@ -120,7 +124,9 @@ export class Input extends FormInput(FormElement(LitElement)) {
120
124
  if (this.hasAttribute("sameValueAs") && this.sameValueAsName) {
121
125
  const formPublisher = this.getFormPublisher();
122
126
  if (!formPublisher) return;
123
- formPublisher[this.sameValueAsName].offAssign(this.sameValueAsHandle);
127
+ traverseDotNotation(formPublisher, this.sameValueAsName).offAssign(
128
+ this.sameValueAsHandle,
129
+ );
124
130
  }
125
131
  }
126
132
 
@@ -47,6 +47,20 @@ With no `size`, the radio follows `md` (1rem).
47
47
  </template>
48
48
  </sonic-code>
49
49
 
50
+ ## Nested keys
51
+
52
+ A dot in `name` writes a nested object in the form store (same as [Submit](/core/components/functional/submit) nested fields).
53
+
54
+ <sonic-code>
55
+ <template>
56
+ <div formDataProvider="radio-nested-demo" class="grid gap-2">
57
+ <sonic-radio name="passenger.seat" value="window" checked>Window</sonic-radio>
58
+ <sonic-radio name="passenger.seat" value="aisle">Aisle</sonic-radio>
59
+ <div>Seat: <sonic-value dataProvider="radio-nested-demo" key="passenger.seat"></sonic-value></div>
60
+ </div>
61
+ </template>
62
+ </sonic-code>
63
+
50
64
  ## Filtering a list
51
65
 
52
66
  The radio group writes one `status` value to the store used as `dataFilterProvider` by a `sonic-queue`. See [Checkbox](/core/components/ui/form/checkbox) for the multi-value version.