@happyvertical/smrt-svelte 0.30.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 (357) hide show
  1. package/AGENTS.md +317 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +185 -0
  5. package/dist/Provider.svelte +204 -0
  6. package/dist/Provider.svelte.d.ts +73 -0
  7. package/dist/Provider.svelte.d.ts.map +1 -0
  8. package/dist/__tests__/app-state.test.js +156 -0
  9. package/dist/__tests__/warm-clients.test.js +186 -0
  10. package/dist/browser-ai/adapters/llm/factory.d.ts +38 -0
  11. package/dist/browser-ai/adapters/llm/factory.d.ts.map +1 -0
  12. package/dist/browser-ai/adapters/llm/factory.js +91 -0
  13. package/dist/browser-ai/adapters/llm/index.d.ts +7 -0
  14. package/dist/browser-ai/adapters/llm/index.d.ts.map +1 -0
  15. package/dist/browser-ai/adapters/llm/index.js +6 -0
  16. package/dist/browser-ai/adapters/llm/types.d.ts +182 -0
  17. package/dist/browser-ai/adapters/llm/types.d.ts.map +1 -0
  18. package/dist/browser-ai/adapters/llm/types.js +43 -0
  19. package/dist/browser-ai/adapters/llm/webllm.d.ts +33 -0
  20. package/dist/browser-ai/adapters/llm/webllm.d.ts.map +1 -0
  21. package/dist/browser-ai/adapters/llm/webllm.js +225 -0
  22. package/dist/browser-ai/adapters/stt/browser-speech.d.ts +31 -0
  23. package/dist/browser-ai/adapters/stt/browser-speech.d.ts.map +1 -0
  24. package/dist/browser-ai/adapters/stt/browser-speech.js +217 -0
  25. package/dist/browser-ai/adapters/stt/factory.d.ts +49 -0
  26. package/dist/browser-ai/adapters/stt/factory.d.ts.map +1 -0
  27. package/dist/browser-ai/adapters/stt/factory.js +110 -0
  28. package/dist/browser-ai/adapters/stt/index.d.ts +9 -0
  29. package/dist/browser-ai/adapters/stt/index.d.ts.map +1 -0
  30. package/dist/browser-ai/adapters/stt/index.js +8 -0
  31. package/dist/browser-ai/adapters/stt/types.d.ts +154 -0
  32. package/dist/browser-ai/adapters/stt/types.d.ts.map +1 -0
  33. package/dist/browser-ai/adapters/stt/types.js +4 -0
  34. package/dist/browser-ai/adapters/stt/whisper-cpp.d.ts +46 -0
  35. package/dist/browser-ai/adapters/stt/whisper-cpp.d.ts.map +1 -0
  36. package/dist/browser-ai/adapters/stt/whisper-cpp.js +348 -0
  37. package/dist/browser-ai/adapters/stt/whisper-wasm.d.ts +51 -0
  38. package/dist/browser-ai/adapters/stt/whisper-wasm.d.ts.map +1 -0
  39. package/dist/browser-ai/adapters/stt/whisper-wasm.js +380 -0
  40. package/dist/browser-ai/adapters/tts/browser-synthesis.d.ts +42 -0
  41. package/dist/browser-ai/adapters/tts/browser-synthesis.d.ts.map +1 -0
  42. package/dist/browser-ai/adapters/tts/browser-synthesis.js +235 -0
  43. package/dist/browser-ai/adapters/tts/factory.d.ts +53 -0
  44. package/dist/browser-ai/adapters/tts/factory.d.ts.map +1 -0
  45. package/dist/browser-ai/adapters/tts/factory.js +92 -0
  46. package/dist/browser-ai/adapters/tts/index.d.ts +7 -0
  47. package/dist/browser-ai/adapters/tts/index.d.ts.map +1 -0
  48. package/dist/browser-ai/adapters/tts/index.js +6 -0
  49. package/dist/browser-ai/adapters/tts/types.d.ts +140 -0
  50. package/dist/browser-ai/adapters/tts/types.d.ts.map +1 -0
  51. package/dist/browser-ai/adapters/tts/types.js +4 -0
  52. package/dist/browser-ai/capabilities/detector.d.ts +38 -0
  53. package/dist/browser-ai/capabilities/detector.d.ts.map +1 -0
  54. package/dist/browser-ai/capabilities/detector.js +211 -0
  55. package/dist/browser-ai/core/errors.d.ts +62 -0
  56. package/dist/browser-ai/core/errors.d.ts.map +1 -0
  57. package/dist/browser-ai/core/errors.js +92 -0
  58. package/dist/browser-ai/core/index.d.ts +6 -0
  59. package/dist/browser-ai/core/index.d.ts.map +1 -0
  60. package/dist/browser-ai/core/index.js +5 -0
  61. package/dist/browser-ai/core/types.d.ts +115 -0
  62. package/dist/browser-ai/core/types.d.ts.map +1 -0
  63. package/dist/browser-ai/core/types.js +34 -0
  64. package/dist/browser-ai/index.d.ts +12 -0
  65. package/dist/browser-ai/index.d.ts.map +1 -0
  66. package/dist/browser-ai/index.js +16 -0
  67. package/dist/browser-ai/svelte/components/AILoadingOverlay.svelte +77 -0
  68. package/dist/browser-ai/svelte/components/AILoadingOverlay.svelte.d.ts +16 -0
  69. package/dist/browser-ai/svelte/components/AILoadingOverlay.svelte.d.ts.map +1 -0
  70. package/dist/browser-ai/svelte/components/CapabilityGate.svelte +57 -0
  71. package/dist/browser-ai/svelte/components/CapabilityGate.svelte.d.ts +15 -0
  72. package/dist/browser-ai/svelte/components/CapabilityGate.svelte.d.ts.map +1 -0
  73. package/dist/browser-ai/svelte/components/DownloadProgress.svelte +141 -0
  74. package/dist/browser-ai/svelte/components/DownloadProgress.svelte.d.ts +15 -0
  75. package/dist/browser-ai/svelte/components/DownloadProgress.svelte.d.ts.map +1 -0
  76. package/dist/browser-ai/svelte/components/STTTest.svelte +379 -0
  77. package/dist/browser-ai/svelte/components/STTTest.svelte.d.ts +9 -0
  78. package/dist/browser-ai/svelte/components/STTTest.svelte.d.ts.map +1 -0
  79. package/dist/browser-ai/svelte/components/VoiceInput.svelte +200 -0
  80. package/dist/browser-ai/svelte/components/VoiceInput.svelte.d.ts +16 -0
  81. package/dist/browser-ai/svelte/components/VoiceInput.svelte.d.ts.map +1 -0
  82. package/dist/browser-ai/svelte/index.d.ts +15 -0
  83. package/dist/browser-ai/svelte/index.d.ts.map +1 -0
  84. package/dist/browser-ai/svelte/index.js +28 -0
  85. package/dist/browser-ai/ui.d.ts +16 -0
  86. package/dist/browser-ai/ui.d.ts.map +1 -0
  87. package/dist/browser-ai/ui.js +67 -0
  88. package/dist/components/admin/AgentAdminPanel.svelte +111 -0
  89. package/dist/components/admin/AgentAdminPanel.svelte.d.ts +25 -0
  90. package/dist/components/admin/AgentAdminPanel.svelte.d.ts.map +1 -0
  91. package/dist/components/admin/AgentAdminTabs.svelte +280 -0
  92. package/dist/components/admin/AgentAdminTabs.svelte.d.ts +23 -0
  93. package/dist/components/admin/AgentAdminTabs.svelte.d.ts.map +1 -0
  94. package/dist/components/admin/AgentSettingsShell.svelte +257 -0
  95. package/dist/components/admin/AgentSettingsShell.svelte.d.ts +33 -0
  96. package/dist/components/admin/AgentSettingsShell.svelte.d.ts.map +1 -0
  97. package/dist/components/admin/index.d.ts +5 -0
  98. package/dist/components/admin/index.d.ts.map +1 -0
  99. package/dist/components/admin/index.js +6 -0
  100. package/dist/components/forms/AddressInput.svelte +500 -0
  101. package/dist/components/forms/AddressInput.svelte.d.ts +36 -0
  102. package/dist/components/forms/AddressInput.svelte.d.ts.map +1 -0
  103. package/dist/components/forms/CheckboxInput.svelte +208 -0
  104. package/dist/components/forms/CheckboxInput.svelte.d.ts +20 -0
  105. package/dist/components/forms/CheckboxInput.svelte.d.ts.map +1 -0
  106. package/dist/components/forms/DateRangeInput.svelte +628 -0
  107. package/dist/components/forms/DateRangeInput.svelte.d.ts +33 -0
  108. package/dist/components/forms/DateRangeInput.svelte.d.ts.map +1 -0
  109. package/dist/components/forms/DateTimeInput.svelte +521 -0
  110. package/dist/components/forms/DateTimeInput.svelte.d.ts +24 -0
  111. package/dist/components/forms/DateTimeInput.svelte.d.ts.map +1 -0
  112. package/dist/components/forms/FileUpload.svelte +358 -0
  113. package/dist/components/forms/FileUpload.svelte.d.ts +22 -0
  114. package/dist/components/forms/FileUpload.svelte.d.ts.map +1 -0
  115. package/dist/components/forms/Form.svelte +771 -0
  116. package/dist/components/forms/Form.svelte.d.ts +26 -0
  117. package/dist/components/forms/Form.svelte.d.ts.map +1 -0
  118. package/dist/components/forms/FormGroup.svelte +86 -0
  119. package/dist/components/forms/FormGroup.svelte.d.ts +13 -0
  120. package/dist/components/forms/FormGroup.svelte.d.ts.map +1 -0
  121. package/dist/components/forms/FormMicButton.svelte +179 -0
  122. package/dist/components/forms/FormMicButton.svelte.d.ts +10 -0
  123. package/dist/components/forms/FormMicButton.svelte.d.ts.map +1 -0
  124. package/dist/components/forms/Input.svelte +83 -0
  125. package/dist/components/forms/Input.svelte.d.ts +9 -0
  126. package/dist/components/forms/Input.svelte.d.ts.map +1 -0
  127. package/dist/components/forms/MeasurementInput.svelte +505 -0
  128. package/dist/components/forms/MeasurementInput.svelte.d.ts +35 -0
  129. package/dist/components/forms/MeasurementInput.svelte.d.ts.map +1 -0
  130. package/dist/components/forms/MoneyInput.svelte +412 -0
  131. package/dist/components/forms/MoneyInput.svelte.d.ts +30 -0
  132. package/dist/components/forms/MoneyInput.svelte.d.ts.map +1 -0
  133. package/dist/components/forms/NumberInput.svelte +310 -0
  134. package/dist/components/forms/NumberInput.svelte.d.ts +28 -0
  135. package/dist/components/forms/NumberInput.svelte.d.ts.map +1 -0
  136. package/dist/components/forms/PhoneInput.svelte +530 -0
  137. package/dist/components/forms/PhoneInput.svelte.d.ts +22 -0
  138. package/dist/components/forms/PhoneInput.svelte.d.ts.map +1 -0
  139. package/dist/components/forms/SearchInput.svelte +358 -0
  140. package/dist/components/forms/SearchInput.svelte.d.ts +33 -0
  141. package/dist/components/forms/SearchInput.svelte.d.ts.map +1 -0
  142. package/dist/components/forms/Select.svelte +83 -0
  143. package/dist/components/forms/Select.svelte.d.ts +11 -0
  144. package/dist/components/forms/Select.svelte.d.ts.map +1 -0
  145. package/dist/components/forms/SelectInput.svelte +254 -0
  146. package/dist/components/forms/SelectInput.svelte.d.ts +25 -0
  147. package/dist/components/forms/SelectInput.svelte.d.ts.map +1 -0
  148. package/dist/components/forms/TextInput.svelte +415 -0
  149. package/dist/components/forms/TextInput.svelte.d.ts +26 -0
  150. package/dist/components/forms/TextInput.svelte.d.ts.map +1 -0
  151. package/dist/components/forms/Textarea.svelte +85 -0
  152. package/dist/components/forms/Textarea.svelte.d.ts +10 -0
  153. package/dist/components/forms/Textarea.svelte.d.ts.map +1 -0
  154. package/dist/components/forms/TextareaInput.svelte +386 -0
  155. package/dist/components/forms/TextareaInput.svelte.d.ts +26 -0
  156. package/dist/components/forms/TextareaInput.svelte.d.ts.map +1 -0
  157. package/dist/components/forms/Toggle.svelte +217 -0
  158. package/dist/components/forms/Toggle.svelte.d.ts +37 -0
  159. package/dist/components/forms/Toggle.svelte.d.ts.map +1 -0
  160. package/dist/components/forms/__tests__/AddressInput.behavior.test.js +122 -0
  161. package/dist/components/forms/__tests__/CheckboxInput.test.js +92 -0
  162. package/dist/components/forms/__tests__/DateRangeInput.behavior.test.js +135 -0
  163. package/dist/components/forms/__tests__/DateTimeInput.behavior.test.js +103 -0
  164. package/dist/components/forms/__tests__/FileUpload.test.js +90 -0
  165. package/dist/components/forms/__tests__/Form.behavior.test.js +137 -0
  166. package/dist/components/forms/__tests__/Form.test.js +58 -0
  167. package/dist/components/forms/__tests__/FormGroup.test.js +48 -0
  168. package/dist/components/forms/__tests__/FormMicButton.test.js +86 -0
  169. package/dist/components/forms/__tests__/Input.test.js +49 -0
  170. package/dist/components/forms/__tests__/MeasurementInput.behavior.test.js +129 -0
  171. package/dist/components/forms/__tests__/MoneyInput.behavior.test.js +124 -0
  172. package/dist/components/forms/__tests__/NumberInput.behavior.test.js +141 -0
  173. package/dist/components/forms/__tests__/PhoneInput.behavior.test.js +96 -0
  174. package/dist/components/forms/__tests__/SearchInput.test.js +79 -0
  175. package/dist/components/forms/__tests__/Select.test.js +37 -0
  176. package/dist/components/forms/__tests__/SelectInput.behavior.test.js +132 -0
  177. package/dist/components/forms/__tests__/TextInput.behavior.test.js +131 -0
  178. package/dist/components/forms/__tests__/Textarea.test.js +39 -0
  179. package/dist/components/forms/__tests__/TextareaInput.behavior.test.js +96 -0
  180. package/dist/components/forms/__tests__/Toggle.test.js +87 -0
  181. package/dist/components/forms/__tests__/composite-inputs-a11y.test.js +69 -0
  182. package/dist/components/forms/__tests__/form-group-input.fixture.svelte +16 -0
  183. package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts +9 -0
  184. package/dist/components/forms/__tests__/form-group-input.fixture.svelte.d.ts.map +1 -0
  185. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte +33 -0
  186. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts +12 -0
  187. package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts.map +1 -0
  188. package/dist/components/forms/__tests__/rich-inputs-a11y.test.js +87 -0
  189. package/dist/components/forms/index.d.ts +25 -0
  190. package/dist/components/forms/index.d.ts.map +1 -0
  191. package/dist/components/forms/index.js +25 -0
  192. package/dist/components/forms/types.d.ts +33 -0
  193. package/dist/components/forms/types.d.ts.map +1 -0
  194. package/dist/components/forms/types.js +4 -0
  195. package/dist/components/module/ModulePanel.svelte +134 -0
  196. package/dist/components/module/ModulePanel.svelte.d.ts +22 -0
  197. package/dist/components/module/ModulePanel.svelte.d.ts.map +1 -0
  198. package/dist/components/module/index.d.ts +5 -0
  199. package/dist/components/module/index.d.ts.map +1 -0
  200. package/dist/components/module/index.js +4 -0
  201. package/dist/components/workspace/Breadcrumbs.svelte +141 -0
  202. package/dist/components/workspace/Breadcrumbs.svelte.d.ts +21 -0
  203. package/dist/components/workspace/Breadcrumbs.svelte.d.ts.map +1 -0
  204. package/dist/components/workspace/NavTree.svelte +354 -0
  205. package/dist/components/workspace/NavTree.svelte.d.ts +45 -0
  206. package/dist/components/workspace/NavTree.svelte.d.ts.map +1 -0
  207. package/dist/components/workspace/README.md +34 -0
  208. package/dist/components/workspace/RoleShell.svelte +309 -0
  209. package/dist/components/workspace/RoleShell.svelte.d.ts +91 -0
  210. package/dist/components/workspace/RoleShell.svelte.d.ts.map +1 -0
  211. package/dist/components/workspace/WorkspaceShell.svelte +951 -0
  212. package/dist/components/workspace/WorkspaceShell.svelte.d.ts +112 -0
  213. package/dist/components/workspace/WorkspaceShell.svelte.d.ts.map +1 -0
  214. package/dist/components/workspace/__tests__/RoleShell.test.js +772 -0
  215. package/dist/components/workspace/__tests__/WorkspaceShell.test.js +630 -0
  216. package/dist/components/workspace/__tests__/breadcrumbs-helpers.test.js +141 -0
  217. package/dist/components/workspace/__tests__/context-forwarding-harness.svelte +45 -0
  218. package/dist/components/workspace/__tests__/context-forwarding-harness.svelte.d.ts +21 -0
  219. package/dist/components/workspace/__tests__/context-forwarding-harness.svelte.d.ts.map +1 -0
  220. package/dist/components/workspace/__tests__/define-tools-dock.test.js +1010 -0
  221. package/dist/components/workspace/__tests__/harness.svelte +25 -0
  222. package/dist/components/workspace/__tests__/harness.svelte.d.ts +14 -0
  223. package/dist/components/workspace/__tests__/harness.svelte.d.ts.map +1 -0
  224. package/dist/components/workspace/__tests__/index.test.js +37 -0
  225. package/dist/components/workspace/__tests__/manifest-nav-helpers.test.js +24 -0
  226. package/dist/components/workspace/__tests__/manifest-nav.test.js +599 -0
  227. package/dist/components/workspace/__tests__/nav-helpers.test.js +95 -0
  228. package/dist/components/workspace/__tests__/render-harness.svelte +66 -0
  229. package/dist/components/workspace/__tests__/render-harness.svelte.d.ts +32 -0
  230. package/dist/components/workspace/__tests__/render-harness.svelte.d.ts.map +1 -0
  231. package/dist/components/workspace/__tests__/render-tools-dock.test.js +243 -0
  232. package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte +58 -0
  233. package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte.d.ts +16 -0
  234. package/dist/components/workspace/__tests__/role-shell-bind-harness.svelte.d.ts.map +1 -0
  235. package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte +41 -0
  236. package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte.d.ts +13 -0
  237. package/dist/components/workspace/__tests__/role-shell-switch-harness.svelte.d.ts.map +1 -0
  238. package/dist/components/workspace/__tests__/test-icon.svelte +17 -0
  239. package/dist/components/workspace/__tests__/test-icon.svelte.d.ts +19 -0
  240. package/dist/components/workspace/__tests__/test-icon.svelte.d.ts.map +1 -0
  241. package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte +38 -0
  242. package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte.d.ts +22 -0
  243. package/dist/components/workspace/__tests__/typed-tool-fixture/TypedTool.svelte.d.ts.map +1 -0
  244. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts +65 -0
  245. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts.map +1 -0
  246. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.js +115 -0
  247. package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.d.ts +15 -0
  248. package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.d.ts.map +1 -0
  249. package/dist/components/workspace/__tests__/typed-tool-fixture/typed-tool-types.js +7 -0
  250. package/dist/components/workspace/__tests__/typed-tool-fixture.test.js +115 -0
  251. package/dist/components/workspace/__tests__/use-harness-orphan.svelte +9 -0
  252. package/dist/components/workspace/__tests__/use-harness-orphan.svelte.d.ts +19 -0
  253. package/dist/components/workspace/__tests__/use-harness-orphan.svelte.d.ts.map +1 -0
  254. package/dist/components/workspace/__tests__/use-harness.svelte +23 -0
  255. package/dist/components/workspace/__tests__/use-harness.svelte.d.ts +8 -0
  256. package/dist/components/workspace/__tests__/use-harness.svelte.d.ts.map +1 -0
  257. package/dist/components/workspace/__tests__/use-tools-dock.test.js +33 -0
  258. package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte +43 -0
  259. package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte.d.ts +11 -0
  260. package/dist/components/workspace/__tests__/workspace-shell-bind-harness.svelte.d.ts.map +1 -0
  261. package/dist/components/workspace/breadcrumbs-helpers.d.ts +44 -0
  262. package/dist/components/workspace/breadcrumbs-helpers.d.ts.map +1 -0
  263. package/dist/components/workspace/breadcrumbs-helpers.js +88 -0
  264. package/dist/components/workspace/index.d.ts +16 -0
  265. package/dist/components/workspace/index.d.ts.map +1 -0
  266. package/dist/components/workspace/index.js +14 -0
  267. package/dist/components/workspace/manifest-nav.d.ts +200 -0
  268. package/dist/components/workspace/manifest-nav.d.ts.map +1 -0
  269. package/dist/components/workspace/manifest-nav.js +408 -0
  270. package/dist/components/workspace/nav-helpers.d.ts +36 -0
  271. package/dist/components/workspace/nav-helpers.d.ts.map +1 -0
  272. package/dist/components/workspace/nav-helpers.js +60 -0
  273. package/dist/components/workspace/server/__tests__/compose-availability.test.js +383 -0
  274. package/dist/components/workspace/server/__tests__/typed-context-fixture.d.ts +78 -0
  275. package/dist/components/workspace/server/__tests__/typed-context-fixture.d.ts.map +1 -0
  276. package/dist/components/workspace/server/__tests__/typed-context-fixture.js +104 -0
  277. package/dist/components/workspace/server/compose-availability.d.ts +73 -0
  278. package/dist/components/workspace/server/compose-availability.d.ts.map +1 -0
  279. package/dist/components/workspace/server/compose-availability.js +114 -0
  280. package/dist/components/workspace/server/index.d.ts +13 -0
  281. package/dist/components/workspace/server/index.d.ts.map +1 -0
  282. package/dist/components/workspace/server/index.js +11 -0
  283. package/dist/components/workspace/server/types.d.ts +108 -0
  284. package/dist/components/workspace/server/types.d.ts.map +1 -0
  285. package/dist/components/workspace/server/types.js +11 -0
  286. package/dist/components/workspace/tools-dock/ToolsDock.svelte +565 -0
  287. package/dist/components/workspace/tools-dock/ToolsDock.svelte.d.ts +14 -0
  288. package/dist/components/workspace/tools-dock/ToolsDock.svelte.d.ts.map +1 -0
  289. package/dist/components/workspace/tools-dock/define-tools-dock.svelte.d.ts +143 -0
  290. package/dist/components/workspace/tools-dock/define-tools-dock.svelte.d.ts.map +1 -0
  291. package/dist/components/workspace/tools-dock/define-tools-dock.svelte.js +487 -0
  292. package/dist/components/workspace/tools-dock/use-tools-dock.d.ts +41 -0
  293. package/dist/components/workspace/tools-dock/use-tools-dock.d.ts.map +1 -0
  294. package/dist/components/workspace/tools-dock/use-tools-dock.js +50 -0
  295. package/dist/components/workspace/types.d.ts +372 -0
  296. package/dist/components/workspace/types.d.ts.map +1 -0
  297. package/dist/components/workspace/types.js +6 -0
  298. package/dist/hooks/index.d.ts +11 -0
  299. package/dist/hooks/index.d.ts.map +1 -0
  300. package/dist/hooks/index.js +10 -0
  301. package/dist/hooks/useAppState.svelte.d.ts +46 -0
  302. package/dist/hooks/useAppState.svelte.d.ts.map +1 -0
  303. package/dist/hooks/useAppState.svelte.js +59 -0
  304. package/dist/hooks/useAuth.svelte.d.ts +41 -0
  305. package/dist/hooks/useAuth.svelte.d.ts.map +1 -0
  306. package/dist/hooks/useAuth.svelte.js +43 -0
  307. package/dist/hooks/useLLM.svelte.d.ts +69 -0
  308. package/dist/hooks/useLLM.svelte.d.ts.map +1 -0
  309. package/dist/hooks/useLLM.svelte.js +85 -0
  310. package/dist/hooks/useSTT.svelte.d.ts +68 -0
  311. package/dist/hooks/useSTT.svelte.d.ts.map +1 -0
  312. package/dist/hooks/useSTT.svelte.js +97 -0
  313. package/dist/hooks/useSocket.svelte.d.ts +45 -0
  314. package/dist/hooks/useSocket.svelte.d.ts.map +1 -0
  315. package/dist/hooks/useSocket.svelte.js +54 -0
  316. package/dist/hooks/useTTS.svelte.d.ts +65 -0
  317. package/dist/hooks/useTTS.svelte.d.ts.map +1 -0
  318. package/dist/hooks/useTTS.svelte.js +93 -0
  319. package/dist/hooks/useTheme.d.ts +13 -0
  320. package/dist/hooks/useTheme.d.ts.map +1 -0
  321. package/dist/hooks/useTheme.js +16 -0
  322. package/dist/i18n/__tests__/server.spec.js +50 -0
  323. package/dist/i18n/server.d.ts +47 -0
  324. package/dist/i18n/server.d.ts.map +1 -0
  325. package/dist/i18n/server.js +58 -0
  326. package/dist/i18n/strings.forms.d.ts +33 -0
  327. package/dist/i18n/strings.forms.d.ts.map +1 -0
  328. package/dist/i18n/strings.forms.js +54 -0
  329. package/dist/i18n/strings.workspace.d.ts +34 -0
  330. package/dist/i18n/strings.workspace.d.ts.map +1 -0
  331. package/dist/i18n/strings.workspace.js +40 -0
  332. package/dist/index.d.ts +18 -0
  333. package/dist/index.d.ts.map +1 -0
  334. package/dist/index.js +23 -0
  335. package/dist/state/__tests__/warm-clients.test.js +40 -0
  336. package/dist/state/app-state.d.ts +308 -0
  337. package/dist/state/app-state.d.ts.map +1 -0
  338. package/dist/state/app-state.js +64 -0
  339. package/dist/state/app-state.svelte.d.ts +196 -0
  340. package/dist/state/app-state.svelte.d.ts.map +1 -0
  341. package/dist/state/app-state.svelte.js +774 -0
  342. package/dist/state/context.d.ts +23 -0
  343. package/dist/state/context.d.ts.map +1 -0
  344. package/dist/state/context.js +32 -0
  345. package/dist/state/form-context.d.ts +59 -0
  346. package/dist/state/form-context.d.ts.map +1 -0
  347. package/dist/state/form-context.js +31 -0
  348. package/dist/state/form-group-context.d.ts +13 -0
  349. package/dist/state/form-group-context.d.ts.map +1 -0
  350. package/dist/state/form-group-context.js +28 -0
  351. package/dist/state/index.d.ts +9 -0
  352. package/dist/state/index.d.ts.map +1 -0
  353. package/dist/state/index.js +8 -0
  354. package/dist/state/warm-clients.d.ts +136 -0
  355. package/dist/state/warm-clients.d.ts.map +1 -0
  356. package/dist/state/warm-clients.js +231 -0
  357. package/package.json +137 -0
@@ -0,0 +1,599 @@
1
+ /**
2
+ * Tests for `navTreeFromManifest` — the pure manifest → NavSection[]
3
+ * adapter. The helper has no Svelte runtime, no SSR coupling, and no
4
+ * SvelteKit imports, so these are vanilla data-in / data-out tests.
5
+ *
6
+ * Brief: happyvertical/smrt#1248 (parent epic #1245). Covers empty
7
+ * manifest, full pass-through, filtered subset, sectionHints application,
8
+ * deterministic ordering, missing api/collection fallback, and the optional
9
+ * `@smrt({ ui: { icon, label } })` decorator field.
10
+ */
11
+ import { describe, expect, it } from 'vitest';
12
+ import { navTreeFromManifest, pluralizeClassName, } from '../manifest-nav.js';
13
+ // ────────────────────────────────────────────────────────────────────────
14
+ // Test fixtures — hand-built manifests that mimic the shape produced by
15
+ // `ManifestBuilder.generate()`. Keep these small and focused; building a
16
+ // realistic-looking manifest from one of the consumer packages would
17
+ // drag a workspace dependency in.
18
+ // ────────────────────────────────────────────────────────────────────────
19
+ const EMPTY_MANIFEST = { objects: {} };
20
+ const TWO_PACKAGE_MANIFEST = {
21
+ objects: {
22
+ '@happyvertical/smrt-content:Article': {
23
+ qualifiedName: '@happyvertical/smrt-content:Article',
24
+ className: 'Article',
25
+ packageName: '@happyvertical/smrt-content',
26
+ collection: 'articles',
27
+ extends: 'SmrtObject',
28
+ decoratorConfig: {},
29
+ },
30
+ '@happyvertical/smrt-content:Document': {
31
+ qualifiedName: '@happyvertical/smrt-content:Document',
32
+ className: 'Document',
33
+ packageName: '@happyvertical/smrt-content',
34
+ collection: 'documents',
35
+ extends: 'SmrtObject',
36
+ decoratorConfig: {},
37
+ },
38
+ '@happyvertical/smrt-content:ArticleCollection': {
39
+ qualifiedName: '@happyvertical/smrt-content:ArticleCollection',
40
+ className: 'ArticleCollection',
41
+ packageName: '@happyvertical/smrt-content',
42
+ collection: 'articles',
43
+ extends: 'SmrtCollection',
44
+ decoratorConfig: {},
45
+ },
46
+ '@happyvertical/smrt-commerce:Invoice': {
47
+ qualifiedName: '@happyvertical/smrt-commerce:Invoice',
48
+ className: 'Invoice',
49
+ packageName: '@happyvertical/smrt-commerce',
50
+ collection: 'invoices',
51
+ extends: 'SmrtObject',
52
+ decoratorConfig: {},
53
+ },
54
+ '@happyvertical/smrt-commerce:Customer': {
55
+ qualifiedName: '@happyvertical/smrt-commerce:Customer',
56
+ className: 'Customer',
57
+ packageName: '@happyvertical/smrt-commerce',
58
+ collection: 'customers',
59
+ extends: 'SmrtObject',
60
+ decoratorConfig: {},
61
+ },
62
+ },
63
+ };
64
+ describe('navTreeFromManifest', () => {
65
+ it('returns an empty array for an empty manifest', () => {
66
+ expect(navTreeFromManifest(EMPTY_MANIFEST)).toEqual([]);
67
+ });
68
+ it('returns an empty array when permittedResources filters out everything', () => {
69
+ expect(navTreeFromManifest(TWO_PACKAGE_MANIFEST, {
70
+ permittedResources: ['@some/other-package:Foo'],
71
+ })).toEqual([]);
72
+ });
73
+ it('emits one section per package with all SmrtObjects when permittedResources is omitted', () => {
74
+ const result = navTreeFromManifest(TWO_PACKAGE_MANIFEST);
75
+ expect(result.map((s) => s.label)).toEqual([
76
+ 'smrt-commerce',
77
+ 'smrt-content',
78
+ ]);
79
+ expect(result[0].children?.map((c) => c.label)).toEqual([
80
+ 'Customers',
81
+ 'Invoices',
82
+ ]);
83
+ expect(result[1].children?.map((c) => c.label)).toEqual([
84
+ 'Articles',
85
+ 'Documents',
86
+ ]);
87
+ });
88
+ it('drops collection classes (extends SmrtCollection or *Collection suffix)', () => {
89
+ const result = navTreeFromManifest(TWO_PACKAGE_MANIFEST);
90
+ const allItemLabels = result.flatMap((s) => s.children?.map((c) => c.label) ?? []);
91
+ // No `ArticleCollection` should appear anywhere.
92
+ expect(allItemLabels).not.toContain('ArticleCollections');
93
+ expect(allItemLabels).not.toContain('Articlecollections');
94
+ });
95
+ it('honours permittedResources by qualified class name', () => {
96
+ const result = navTreeFromManifest(TWO_PACKAGE_MANIFEST, {
97
+ permittedResources: [
98
+ '@happyvertical/smrt-content:Article',
99
+ '@happyvertical/smrt-commerce:Invoice',
100
+ ],
101
+ });
102
+ expect(result).toHaveLength(2);
103
+ expect(result.map((s) => s.label)).toEqual([
104
+ 'smrt-commerce',
105
+ 'smrt-content',
106
+ ]);
107
+ expect(result.flatMap((s) => s.children?.map((c) => c.label) ?? [])).toEqual(['Invoices', 'Articles']);
108
+ });
109
+ it('applies sectionHints to override the default section title', () => {
110
+ const result = navTreeFromManifest(TWO_PACKAGE_MANIFEST, {
111
+ sectionHints: {
112
+ '@happyvertical/smrt-content': 'Content',
113
+ '@happyvertical/smrt-commerce': 'Commerce',
114
+ },
115
+ });
116
+ expect(result.map((s) => s.label)).toEqual(['Commerce', 'Content']);
117
+ });
118
+ it('groups multiple packages under a single section when a sectionHint catches both', () => {
119
+ const manifest = {
120
+ objects: {
121
+ '@happyvertical/smrt-content:Article': {
122
+ qualifiedName: '@happyvertical/smrt-content:Article',
123
+ className: 'Article',
124
+ packageName: '@happyvertical/smrt-content',
125
+ collection: 'articles',
126
+ decoratorConfig: {},
127
+ },
128
+ '@happyvertical/smrt-messages:Email': {
129
+ qualifiedName: '@happyvertical/smrt-messages:Email',
130
+ className: 'Email',
131
+ packageName: '@happyvertical/smrt-messages',
132
+ collection: 'emails',
133
+ decoratorConfig: {},
134
+ },
135
+ },
136
+ };
137
+ const result = navTreeFromManifest(manifest, {
138
+ sectionHints: {
139
+ // Catch both content + messages with the @happyvertical org prefix.
140
+ '@happyvertical/': 'Content & Messaging',
141
+ },
142
+ });
143
+ expect(result).toHaveLength(1);
144
+ expect(result[0].label).toBe('Content & Messaging');
145
+ expect(result[0].children?.map((c) => c.label)).toEqual([
146
+ 'Articles',
147
+ 'Emails',
148
+ ]);
149
+ });
150
+ it('produces deterministic ordering (sorted by label) regardless of input order', () => {
151
+ // Build the same manifest twice, with reversed key insertion order.
152
+ const a = {
153
+ objects: {
154
+ 'pkg:Banana': {
155
+ qualifiedName: 'pkg:Banana',
156
+ className: 'Banana',
157
+ packageName: 'pkg',
158
+ collection: 'bananas',
159
+ decoratorConfig: {},
160
+ },
161
+ 'pkg:Apple': {
162
+ qualifiedName: 'pkg:Apple',
163
+ className: 'Apple',
164
+ packageName: 'pkg',
165
+ collection: 'apples',
166
+ decoratorConfig: {},
167
+ },
168
+ },
169
+ };
170
+ const b = {
171
+ objects: {
172
+ 'pkg:Apple': a.objects['pkg:Apple'],
173
+ 'pkg:Banana': a.objects['pkg:Banana'],
174
+ },
175
+ };
176
+ const fromA = navTreeFromManifest(a);
177
+ const fromB = navTreeFromManifest(b);
178
+ expect(fromA).toEqual(fromB);
179
+ expect(fromA[0].children?.map((c) => c.label)).toEqual([
180
+ 'Apples',
181
+ 'Bananas',
182
+ ]);
183
+ });
184
+ it('uses /api/v1/{collection} as the default href for an entry', () => {
185
+ const result = navTreeFromManifest(TWO_PACKAGE_MANIFEST, {
186
+ permittedResources: ['@happyvertical/smrt-content:Article'],
187
+ });
188
+ expect(result[0].children?.[0].href).toBe('/api/v1/articles');
189
+ });
190
+ it('falls back to a kebab-cased class slug when collection is missing', () => {
191
+ const manifest = {
192
+ objects: {
193
+ 'pkg:OrderLine': {
194
+ qualifiedName: 'pkg:OrderLine',
195
+ className: 'OrderLine',
196
+ packageName: 'pkg',
197
+ // collection intentionally omitted
198
+ decoratorConfig: {},
199
+ },
200
+ },
201
+ };
202
+ const result = navTreeFromManifest(manifest, { basePath: '' });
203
+ expect(result[0].children?.[0].href).toBe('/order-line');
204
+ });
205
+ it('honours a custom basePath (e.g. "" for unprefixed hrefs)', () => {
206
+ const result = navTreeFromManifest(TWO_PACKAGE_MANIFEST, {
207
+ basePath: '',
208
+ permittedResources: ['@happyvertical/smrt-content:Article'],
209
+ });
210
+ expect(result[0].children?.[0].href).toBe('/articles');
211
+ });
212
+ it('passes the ui.icon decorator field through to NavItem.icon', () => {
213
+ const manifest = {
214
+ objects: {
215
+ 'pkg:Article': {
216
+ qualifiedName: 'pkg:Article',
217
+ className: 'Article',
218
+ packageName: 'pkg',
219
+ collection: 'articles',
220
+ decoratorConfig: { ui: { icon: 'newspaper' } },
221
+ },
222
+ },
223
+ };
224
+ const result = navTreeFromManifest(manifest);
225
+ expect(result[0].children?.[0].icon).toBe('newspaper');
226
+ });
227
+ it('uses ui.label to override the auto-pluralised label', () => {
228
+ const manifest = {
229
+ objects: {
230
+ 'pkg:Person': {
231
+ qualifiedName: 'pkg:Person',
232
+ className: 'Person',
233
+ packageName: 'pkg',
234
+ collection: 'people',
235
+ // Use a custom label rather than relying on the simple pluralizer
236
+ // (which would emit "Persons").
237
+ decoratorConfig: { ui: { label: 'People' } },
238
+ },
239
+ },
240
+ };
241
+ const result = navTreeFromManifest(manifest);
242
+ expect(result[0].children?.[0].label).toBe('People');
243
+ });
244
+ it('omits NavItem.icon when ui.icon is absent (no empty-string leakage)', () => {
245
+ const result = navTreeFromManifest(TWO_PACKAGE_MANIFEST, {
246
+ permittedResources: ['@happyvertical/smrt-content:Article'],
247
+ });
248
+ expect(result[0].children?.[0]).not.toHaveProperty('icon');
249
+ });
250
+ it('falls back to packageName-based section title when no hint matches', () => {
251
+ const manifest = {
252
+ objects: {
253
+ '@acme/widgets:Sprocket': {
254
+ qualifiedName: '@acme/widgets:Sprocket',
255
+ className: 'Sprocket',
256
+ packageName: '@acme/widgets',
257
+ collection: 'sprockets',
258
+ decoratorConfig: {},
259
+ },
260
+ },
261
+ };
262
+ const result = navTreeFromManifest(manifest, {
263
+ sectionHints: { '@nonmatching/foo': 'Foo' },
264
+ });
265
+ expect(result[0].label).toBe('widgets');
266
+ });
267
+ it('matches sectionHints by substring (first match wins)', () => {
268
+ const manifest = {
269
+ objects: {
270
+ '@happyvertical/smrt-content:Article': {
271
+ qualifiedName: '@happyvertical/smrt-content:Article',
272
+ className: 'Article',
273
+ packageName: '@happyvertical/smrt-content',
274
+ collection: 'articles',
275
+ decoratorConfig: {},
276
+ },
277
+ },
278
+ };
279
+ // Iteration order on an object literal preserves insertion order in
280
+ // modern engines — `@happyvertical/` is checked first and wins.
281
+ const result = navTreeFromManifest(manifest, {
282
+ sectionHints: {
283
+ '@happyvertical/': 'HV',
284
+ 'smrt-content': 'Content',
285
+ },
286
+ });
287
+ expect(result[0].label).toBe('HV');
288
+ });
289
+ // ──────────────────────────────────────────────────────────────────────
290
+ // Visibility filtering — `@smrt({ visibility: 'internal' | 'test' })`
291
+ // marks classes as plumbing / fixtures that should never appear in
292
+ // admin nav, regardless of whether they have a REST list route.
293
+ // ──────────────────────────────────────────────────────────────────────
294
+ it('drops entries with top-level visibility = "internal"', () => {
295
+ const manifest = {
296
+ objects: {
297
+ '@acme/widgets:Public': {
298
+ qualifiedName: '@acme/widgets:Public',
299
+ className: 'Public',
300
+ packageName: '@acme/widgets',
301
+ collection: 'publics',
302
+ decoratorConfig: {},
303
+ },
304
+ '@acme/widgets:InternalJoin': {
305
+ qualifiedName: '@acme/widgets:InternalJoin',
306
+ className: 'InternalJoin',
307
+ packageName: '@acme/widgets',
308
+ collection: 'internal-joins',
309
+ visibility: 'internal',
310
+ decoratorConfig: {},
311
+ },
312
+ },
313
+ };
314
+ const result = navTreeFromManifest(manifest);
315
+ expect(result).toHaveLength(1);
316
+ expect(result[0].children).toHaveLength(1);
317
+ expect(result[0].children?.[0].label).toBe('Publics');
318
+ });
319
+ it('drops entries with visibility = "test" (decoratorConfig fallback)', () => {
320
+ const manifest = {
321
+ objects: {
322
+ '@acme/widgets:Public': {
323
+ qualifiedName: '@acme/widgets:Public',
324
+ className: 'Public',
325
+ packageName: '@acme/widgets',
326
+ collection: 'publics',
327
+ decoratorConfig: {},
328
+ },
329
+ '@acme/widgets:TestFixture': {
330
+ qualifiedName: '@acme/widgets:TestFixture',
331
+ className: 'TestFixture',
332
+ packageName: '@acme/widgets',
333
+ collection: 'test-fixtures',
334
+ // No top-level visibility — the helper falls back to decoratorConfig.
335
+ decoratorConfig: { visibility: 'test' },
336
+ },
337
+ },
338
+ };
339
+ const result = navTreeFromManifest(manifest);
340
+ expect(result).toHaveLength(1);
341
+ expect(result[0].children?.map((c) => c.label)).toEqual(['Publics']);
342
+ });
343
+ it('suppresses STI subtype entries that share their parent collection', () => {
344
+ // SMRT's scanner copies the STI parent's `collection` field onto
345
+ // every child entry. Without dedup, the nav helper would emit nine
346
+ // links for the Contract STI hierarchy that all point at
347
+ // /api/v1/contracts — confusing to users and a footgun for
348
+ // role-permission filtering. The parent's link covers the entire
349
+ // polymorphic list at that endpoint.
350
+ const manifest = {
351
+ objects: {
352
+ '@acme/shop:Contract': {
353
+ qualifiedName: '@acme/shop:Contract',
354
+ className: 'Contract',
355
+ packageName: '@acme/shop',
356
+ collection: 'contracts',
357
+ extends: 'SmrtObject',
358
+ decoratorConfig: {},
359
+ },
360
+ '@acme/shop:Order': {
361
+ qualifiedName: '@acme/shop:Order',
362
+ className: 'Order',
363
+ packageName: '@acme/shop',
364
+ collection: 'contracts',
365
+ extends: 'Contract',
366
+ decoratorConfig: {},
367
+ },
368
+ '@acme/shop:Cart': {
369
+ qualifiedName: '@acme/shop:Cart',
370
+ className: 'Cart',
371
+ packageName: '@acme/shop',
372
+ collection: 'contracts',
373
+ extends: 'Contract',
374
+ decoratorConfig: {},
375
+ },
376
+ },
377
+ };
378
+ const result = navTreeFromManifest(manifest);
379
+ expect(result).toHaveLength(1);
380
+ expect(result[0].children).toHaveLength(1);
381
+ expect(result[0].children?.[0].label).toBe('Contracts');
382
+ expect(result[0].children?.[0].href).toBe('/api/v1/contracts');
383
+ });
384
+ it('keeps the base nav link when only an STI subtype is in permittedResources', () => {
385
+ // Regression for the round-12 finding: when a role's
386
+ // `permittedResources` only lists an STI subtype (e.g. Cart) and
387
+ // not its base (Contract), the dedup step would suppress Cart
388
+ // (subtype sharing parent collection) and the permission filter
389
+ // would drop Contract — leaving the role with no link to the
390
+ // /api/v1/contracts polymorphic endpoint. The expansion remaps
391
+ // each permitted subtype to its parent's qualifier when they
392
+ // share the same collection.
393
+ const manifest = {
394
+ objects: {
395
+ '@acme/shop:Contract': {
396
+ qualifiedName: '@acme/shop:Contract',
397
+ className: 'Contract',
398
+ packageName: '@acme/shop',
399
+ collection: 'contracts',
400
+ extends: 'SmrtObject',
401
+ decoratorConfig: {},
402
+ },
403
+ '@acme/shop:Cart': {
404
+ qualifiedName: '@acme/shop:Cart',
405
+ className: 'Cart',
406
+ packageName: '@acme/shop',
407
+ collection: 'contracts',
408
+ extends: 'Contract',
409
+ decoratorConfig: {},
410
+ },
411
+ '@acme/shop:Order': {
412
+ qualifiedName: '@acme/shop:Order',
413
+ className: 'Order',
414
+ packageName: '@acme/shop',
415
+ collection: 'contracts',
416
+ extends: 'Contract',
417
+ decoratorConfig: {},
418
+ },
419
+ },
420
+ };
421
+ const result = navTreeFromManifest(manifest, {
422
+ permittedResources: ['@acme/shop:Cart'],
423
+ });
424
+ // Role gets one nav link to the shared /api/v1/contracts route,
425
+ // via the base entry (Contract) — Cart was suppressed by the
426
+ // dedup but the permission expansion re-introduced Contract.
427
+ expect(result).toHaveLength(1);
428
+ expect(result[0].children).toHaveLength(1);
429
+ expect(result[0].children?.[0].label).toBe('Contracts');
430
+ expect(result[0].children?.[0].href).toBe('/api/v1/contracts');
431
+ });
432
+ it('suppresses multi-level STI grandchildren even when the intermediate parent is absent', () => {
433
+ // Regression for the round-7 finding: a partial manifest with
434
+ // Product (root) and FabricMaterial (grandchild) but no Material
435
+ // (intermediate) would previously let FabricMaterial through and
436
+ // double up the /api/v1/products link. The recursive walk follows
437
+ // the chain through the missing intermediate by looking up
438
+ // `extends` names directly in the manifest.
439
+ const manifest = {
440
+ objects: {
441
+ '@acme/x:Product': {
442
+ qualifiedName: '@acme/x:Product',
443
+ className: 'Product',
444
+ packageName: '@acme/x',
445
+ collection: 'products',
446
+ extends: 'SmrtObject',
447
+ decoratorConfig: {},
448
+ },
449
+ // Material is declared but intentionally NOT in the manifest
450
+ // (simulated partial / split-package scenario). Its grandchild
451
+ // points at `extends: 'Material'`.
452
+ '@acme/x:FabricMaterial': {
453
+ qualifiedName: '@acme/x:FabricMaterial',
454
+ className: 'FabricMaterial',
455
+ packageName: '@acme/x',
456
+ collection: 'products',
457
+ extends: 'Material',
458
+ decoratorConfig: {},
459
+ },
460
+ },
461
+ };
462
+ const result = navTreeFromManifest(manifest);
463
+ // FabricMaterial dropped — its `extends: 'Material'` doesn't
464
+ // resolve, so the chain walk halts at the missing intermediate.
465
+ // That's the documented "fully-orphaned entries stay visible"
466
+ // behavior; we get one Product link AND one FabricMaterial link.
467
+ // This test asserts the current behavior so we notice if it changes.
468
+ expect(result[0].children?.map((c) => c.label).sort()).toEqual([
469
+ 'FabricMaterials',
470
+ 'Products',
471
+ ]);
472
+ });
473
+ it('suppresses multi-level STI grandchildren when the intermediate parent IS present', () => {
474
+ // Same shape as above but with the intermediate `Material` entry
475
+ // restored. Now the recursive walk: FabricMaterial → Material →
476
+ // Product. Material itself drops because its collection matches
477
+ // Product's. FabricMaterial drops because the same chain reaches
478
+ // Product (matching collection). Only Product survives.
479
+ const manifest = {
480
+ objects: {
481
+ '@acme/x:Product': {
482
+ qualifiedName: '@acme/x:Product',
483
+ className: 'Product',
484
+ packageName: '@acme/x',
485
+ collection: 'products',
486
+ extends: 'SmrtObject',
487
+ decoratorConfig: {},
488
+ },
489
+ '@acme/x:Material': {
490
+ qualifiedName: '@acme/x:Material',
491
+ className: 'Material',
492
+ packageName: '@acme/x',
493
+ collection: 'products',
494
+ extends: 'Product',
495
+ decoratorConfig: {},
496
+ },
497
+ '@acme/x:FabricMaterial': {
498
+ qualifiedName: '@acme/x:FabricMaterial',
499
+ className: 'FabricMaterial',
500
+ packageName: '@acme/x',
501
+ collection: 'products',
502
+ extends: 'Material',
503
+ decoratorConfig: {},
504
+ },
505
+ },
506
+ };
507
+ const result = navTreeFromManifest(manifest);
508
+ expect(result).toHaveLength(1);
509
+ expect(result[0].children).toHaveLength(1);
510
+ expect(result[0].children?.[0].label).toBe('Products');
511
+ });
512
+ it('keeps STI subtypes whose collection differs from the parent', () => {
513
+ // If a subtype overrides `@smrt({ tableName/collection })`, its
514
+ // route IS distinct from the parent's and the nav helper should
515
+ // keep both. Tests the `entry.collection === parent.collection`
516
+ // half of the gate — not just the `entry.extends !== 'SmrtObject'` half.
517
+ const manifest = {
518
+ objects: {
519
+ '@acme/x:Asset': {
520
+ qualifiedName: '@acme/x:Asset',
521
+ className: 'Asset',
522
+ packageName: '@acme/x',
523
+ collection: 'assets',
524
+ extends: 'SmrtObject',
525
+ decoratorConfig: {},
526
+ },
527
+ '@acme/x:Image': {
528
+ qualifiedName: '@acme/x:Image',
529
+ className: 'Image',
530
+ packageName: '@acme/x',
531
+ collection: 'images', // distinct route
532
+ extends: 'Asset',
533
+ decoratorConfig: {},
534
+ },
535
+ },
536
+ };
537
+ const result = navTreeFromManifest(manifest);
538
+ expect(result[0].children?.map((c) => c.label).sort()).toEqual([
539
+ 'Assets',
540
+ 'Images',
541
+ ]);
542
+ });
543
+ it('keeps entries with visibility = "public" or unset', () => {
544
+ const manifest = {
545
+ objects: {
546
+ '@acme/widgets:Explicit': {
547
+ qualifiedName: '@acme/widgets:Explicit',
548
+ className: 'Explicit',
549
+ packageName: '@acme/widgets',
550
+ collection: 'explicits',
551
+ visibility: 'public',
552
+ decoratorConfig: {},
553
+ },
554
+ '@acme/widgets:Implicit': {
555
+ qualifiedName: '@acme/widgets:Implicit',
556
+ className: 'Implicit',
557
+ packageName: '@acme/widgets',
558
+ collection: 'implicits',
559
+ decoratorConfig: {},
560
+ },
561
+ },
562
+ };
563
+ const result = navTreeFromManifest(manifest);
564
+ expect(result[0].children?.map((c) => c.label).sort()).toEqual([
565
+ 'Explicits',
566
+ 'Implicits',
567
+ ]);
568
+ });
569
+ });
570
+ // ────────────────────────────────────────────────────────────────────────
571
+ // Pluralization unit tests — the helper is exported for completeness; most
572
+ // callers should rely on `navTreeFromManifest` to apply it under the hood
573
+ // and override exotic plurals via `@smrt({ ui: { label } })`.
574
+ // ────────────────────────────────────────────────────────────────────────
575
+ describe('pluralizeClassName', () => {
576
+ it('handles simple consonant suffixes', () => {
577
+ expect(pluralizeClassName('Article')).toBe('Articles');
578
+ expect(pluralizeClassName('Product')).toBe('Products');
579
+ });
580
+ it('handles consonant + y → ies', () => {
581
+ expect(pluralizeClassName('Category')).toBe('Categories');
582
+ expect(pluralizeClassName('Currency')).toBe('Currencies');
583
+ });
584
+ it('handles vowel + y → s (no -ies)', () => {
585
+ expect(pluralizeClassName('Survey')).toBe('Surveys');
586
+ });
587
+ it('handles s, x, z, ch, sh suffixes → es', () => {
588
+ expect(pluralizeClassName('Box')).toBe('Boxes');
589
+ expect(pluralizeClassName('Dish')).toBe('Dishes');
590
+ expect(pluralizeClassName('Watch')).toBe('Watches');
591
+ });
592
+ it('passes already-plural words through', () => {
593
+ expect(pluralizeClassName('Articles')).toBe('Articles');
594
+ expect(pluralizeClassName('Categories')).toBe('Categories');
595
+ });
596
+ it('returns empty string unchanged', () => {
597
+ expect(pluralizeClassName('')).toBe('');
598
+ });
599
+ });