@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,951 @@
1
+ <script lang="ts">
2
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
+ import type { Snippet } from 'svelte';
4
+ import { M } from '../../i18n/strings.workspace.js';
5
+
6
+ const { t } = useI18n();
7
+
8
+ /**
9
+ * WorkspaceShell — SvelteKit-agnostic, SSR-safe admin shell primitive.
10
+ *
11
+ * A composition of snippet slots that lays out a typical workspace UI: brand
12
+ * + nav sidebar, sticky topbar, main content, optional right-side inspector
13
+ * panel and a thin icon rail. The component owns no domain logic — every
14
+ * visible region (brand, nav, account menu, tools dock, etc.) is provided by
15
+ * the consumer via Svelte 5 snippets.
16
+ *
17
+ * Responsive behavior:
18
+ * - Desktop (>1240px): sidebar visible, inspector fixed-right when open.
19
+ * - Tablet (961–1240px): sidebar collapses to icon rail.
20
+ * - Mobile (≤960px): sidebar becomes a drawer with backdrop; inspector
21
+ * becomes a right-side drawer with backdrop.
22
+ *
23
+ * Sidebar collapse is controlled — consumer owns persistence. Mobile drawer
24
+ * state defaults to internal/transient but can be lifted via the bindable
25
+ * `mobileNavOpen` prop (see below) for consumers that need to close the
26
+ * drawer in response to events the shell doesn't own (e.g. a route change
27
+ * fired from a nav-tree click). Pair `bind:mobileNavOpen={...}` on the
28
+ * shell with `<NavTree onNavigate={() => (mobileNavOpen = false)} />`
29
+ * inside the `nav` snippet to close the drawer on every link click —
30
+ * no DOM querying required.
31
+ *
32
+ * See `@happyvertical/smrt#1227` for the issue tracking this primitive,
33
+ * and `happyvertical/smrt#1235` for the bindable-drawer follow-up.
34
+ */
35
+
36
+ export interface Props {
37
+ /** Brand/product title shown in the brand snippet area fallback. */
38
+ title?: string;
39
+ /** Secondary line under the title. */
40
+ subtitle?: string;
41
+ /** Small uppercase eyebrow label rendered above the title. */
42
+ eyebrow?: string;
43
+ /** Topbar mode label (e.g. "Local-first mode"). */
44
+ modeLabel?: string;
45
+ /** Status keyword that maps to a dot color. */
46
+ modeStatus?:
47
+ | 'active'
48
+ | 'offline'
49
+ | 'local-only'
50
+ | 'attention'
51
+ | (string & {});
52
+ /** Secondary detail line shown next to the mode badge. */
53
+ modeDetail?: string;
54
+ /** Whether the inspector panel/drawer is currently shown. */
55
+ showInspector?: boolean;
56
+ /**
57
+ * Label rendered in the inspector header. Defaults to `'Inspector'`.
58
+ * Consumers may rename to `'Properties'`, `'Details'`, etc.
59
+ */
60
+ inspectorTitle?: string;
61
+ /**
62
+ * Invoked when the inspector close button (or Escape) is activated.
63
+ *
64
+ * Focus restoration: because `showInspector` is a controlled prop, the
65
+ * consumer owns when the inspector opens and is therefore responsible
66
+ * for restoring focus to the activating element after close (WCAG
67
+ * 2.4.3). A common pattern is to capture `document.activeElement`
68
+ * immediately before flipping `showInspector` to `true`, then call
69
+ * `.focus()` on it inside this handler.
70
+ *
71
+ * The component-owned mobile drawer handles focus restoration
72
+ * internally — only the inspector is consumer-driven.
73
+ */
74
+ onCloseInspector?: () => void;
75
+ /** Sidebar collapsed state (controlled by consumer). */
76
+ collapsed?: boolean;
77
+ /** Invoked when the internal collapse toggle is clicked. */
78
+ onToggleCollapsed?: () => void;
79
+ /** Whether to render the collapse toggle at all. Default: true. */
80
+ collapsible?: boolean;
81
+ /** Top-left brand area (logo + product name). */
82
+ brand?: Snippet;
83
+ /** Main sidebar navigation region. */
84
+ nav?: Snippet;
85
+ /** Bottom of sidebar (account menu, tenant switcher, etc.). */
86
+ sidebarFooter?: Snippet;
87
+ /** Right side of the top bar. */
88
+ topbarActions?: Snippet;
89
+ /** Optional vertical icon rail along the right edge. */
90
+ inspectorRail?: Snippet;
91
+ /**
92
+ * Inspector panel content (right side). Rendered into a `position: fixed`
93
+ * panel anchored to the right edge — z-index `22` on desktop, `22` on
94
+ * mobile (with a `21` scrim).
95
+ *
96
+ * **Positioning conflict**: do NOT use this snippet simultaneously with
97
+ * `<ToolsDock layout='topbar'>`. In topbar mode, the dock owns its own
98
+ * `position: fixed` panel anchored to the bottom-right (z-index `40`)
99
+ * with no z-index coordination against the shell's inspector — both
100
+ * panels will visibly overlap and compete for clicks. Pick one:
101
+ * - For consumer-driven inspector content → use this snippet only,
102
+ * and use `<ToolsDock layout='rail'>` (the rail layout renders its
103
+ * panel inside its own aside, no positioning conflict)
104
+ * - For tools-dock-driven content → leave this snippet unused and
105
+ * render `<ToolsDock layout='topbar'>` inside `topbarActions`
106
+ */
107
+ inspector?: Snippet;
108
+ /** Main content. */
109
+ children: Snippet;
110
+ /**
111
+ * Mobile drawer open-state. Defaults to `false` and is managed
112
+ * internally — the hamburger button, backdrop, and Escape key all
113
+ * toggle it. Consumers who need to close the drawer in response to
114
+ * events outside the shell (e.g. a nav-link click in their nav
115
+ * snippet) can lift the state via `bind:mobileNavOpen={...}` and
116
+ * mutate it directly. SSR-safe — initial value is honored on the
117
+ * server (the drawer is only visible on the ≤960px breakpoint).
118
+ */
119
+ mobileNavOpen?: boolean;
120
+ }
121
+
122
+ let {
123
+ title = '',
124
+ subtitle = '',
125
+ eyebrow = '',
126
+ modeLabel = '',
127
+ modeStatus = '',
128
+ modeDetail = '',
129
+ showInspector = false,
130
+ inspectorTitle = 'Inspector',
131
+ onCloseInspector,
132
+ collapsed = false,
133
+ onToggleCollapsed,
134
+ collapsible = true,
135
+ brand,
136
+ nav,
137
+ sidebarFooter,
138
+ topbarActions,
139
+ inspectorRail,
140
+ inspector,
141
+ children,
142
+ mobileNavOpen = $bindable(false),
143
+ }: Props = $props();
144
+
145
+ /**
146
+ * Tracks whether the viewport is in the mobile drawer breakpoint
147
+ * (≤960px). Used to mark the sidebar `inert` when it's slid off-screen
148
+ * so keyboard users can't tab into invisible nav links. SSR-safe — stays
149
+ * `false` until mounted, which matches the desktop-first default layout.
150
+ */
151
+ let isMobileViewport = $state(false);
152
+ /**
153
+ * Element that held focus when the mobile drawer was opened. We restore
154
+ * focus to it when the drawer closes (Escape, backdrop click, etc.) to
155
+ * satisfy WCAG 2.4.3 Focus Order for the component-owned drawer state.
156
+ * The inspector's open state is consumer-controlled, so focus restoration
157
+ * for it is documented as a caller responsibility (see `onCloseInspector`).
158
+ */
159
+ let lastFocusedBeforeMobileNav: HTMLElement | null = null;
160
+
161
+ function toggleMobileNav(): void {
162
+ if (!mobileNavOpen) {
163
+ // Capture the currently focused element before we open the drawer so
164
+ // we can return focus to it on close (WCAG 2.4.3).
165
+ if (typeof document !== 'undefined') {
166
+ const active = document.activeElement;
167
+ lastFocusedBeforeMobileNav =
168
+ active instanceof HTMLElement ? active : null;
169
+ }
170
+ mobileNavOpen = true;
171
+ } else {
172
+ closeMobileNav();
173
+ }
174
+ }
175
+
176
+ function closeMobileNav(): void {
177
+ mobileNavOpen = false;
178
+ // Restore focus to the element that opened the drawer (the hamburger
179
+ // button in the default flow). Guarded so SSR/non-DOM environments are
180
+ // a no-op, and so a detached/removed element doesn't throw.
181
+ const target = lastFocusedBeforeMobileNav;
182
+ lastFocusedBeforeMobileNav = null;
183
+ if (
184
+ target &&
185
+ typeof document !== 'undefined' &&
186
+ document.contains(target) &&
187
+ typeof target.focus === 'function'
188
+ ) {
189
+ target.focus();
190
+ }
191
+ }
192
+
193
+ function handleCollapseToggle(): void {
194
+ onToggleCollapsed?.();
195
+ }
196
+
197
+ function handleInspectorClose(): void {
198
+ onCloseInspector?.();
199
+ }
200
+
201
+ // Escape closes mobile drawer (with focus restoration) or inspector if
202
+ // open. Mounted-only (SSR-safe via $effect).
203
+ $effect(() => {
204
+ if (typeof window === 'undefined') return;
205
+
206
+ function onKeydown(event: KeyboardEvent) {
207
+ if (event.key !== 'Escape') return;
208
+ if (mobileNavOpen) {
209
+ closeMobileNav();
210
+ return;
211
+ }
212
+ if (showInspector && onCloseInspector) {
213
+ onCloseInspector();
214
+ }
215
+ }
216
+
217
+ window.addEventListener('keydown', onKeydown);
218
+ return () => window.removeEventListener('keydown', onKeydown);
219
+ });
220
+
221
+ // Track mobile breakpoint so the sidebar can be marked `inert` while it
222
+ // is slid off-screen. Mirrors the `@media (max-width: 960px)` block.
223
+ $effect(() => {
224
+ if (typeof window === 'undefined' || !window.matchMedia) return;
225
+
226
+ const mql = window.matchMedia('(max-width: 960px)');
227
+ isMobileViewport = mql.matches;
228
+
229
+ function handleChange(event: MediaQueryListEvent) {
230
+ isMobileViewport = event.matches;
231
+ }
232
+
233
+ // `addEventListener` is the modern API. Older Safari only has
234
+ // `addListener` — feature-detect to stay compatible. The deprecated
235
+ // `addListener`/`removeListener` methods are typed in current lib.dom.d.ts,
236
+ // so no `@ts-expect-error` is needed.
237
+ if (typeof mql.addEventListener === 'function') {
238
+ mql.addEventListener('change', handleChange);
239
+ return () => mql.removeEventListener('change', handleChange);
240
+ }
241
+ mql.addListener(handleChange);
242
+ return () => {
243
+ mql.removeListener(handleChange);
244
+ };
245
+ });
246
+
247
+ const hasInspector = $derived(Boolean(inspector) && showInspector);
248
+ const hasInspectorRail = $derived(Boolean(inspectorRail));
249
+ /**
250
+ * Mark the sidebar `inert` when it's slid off-screen on mobile so keyboard
251
+ * users can't tab into invisible nav links / footer controls.
252
+ */
253
+ const sidebarInert = $derived(isMobileViewport && !mobileNavOpen);
254
+ </script>
255
+
256
+ <div
257
+ class="smrt-workspace-shell"
258
+ class:sidebar-collapsed={collapsed}
259
+ class:nav-open={mobileNavOpen}
260
+ class:has-inspector={hasInspector}
261
+ class:has-inspector-rail={hasInspectorRail}
262
+ >
263
+ <!-- Mobile sidebar backdrop -->
264
+ <button
265
+ class="mobile-backdrop"
266
+ type="button"
267
+ aria-label={t(M['ui.workspace_shell.close_navigation'])}
268
+ tabindex={mobileNavOpen ? 0 : -1}
269
+ onclick={closeMobileNav}
270
+ ></button>
271
+
272
+ <!--
273
+ Mobile inspector backdrop. Only renders an interactive button when the
274
+ consumer provided `onCloseInspector` — otherwise the backdrop would be a
275
+ focusable no-op. When no close handler is provided we render a passive
276
+ `<div>` so the visual scrim still appears on mobile.
277
+ -->
278
+ {#if hasInspector}
279
+ {#if onCloseInspector}
280
+ <button
281
+ class="inspector-backdrop"
282
+ type="button"
283
+ aria-label={t(M['ui.workspace_shell.close_inspector'])}
284
+ onclick={handleInspectorClose}
285
+ ></button>
286
+ {:else}
287
+ <div class="inspector-backdrop" aria-hidden="true"></div>
288
+ {/if}
289
+ {/if}
290
+
291
+ <aside
292
+ class="smrt-workspace-sidebar"
293
+ aria-label={t(M['ui.workspace_shell.primary_navigation'])}
294
+ inert={sidebarInert}
295
+ >
296
+ <div class="brand-row">
297
+ <div class="brand">
298
+ {#if brand}
299
+ {@render brand()}
300
+ {:else}
301
+ {#if eyebrow}
302
+ <span class="eyebrow">{eyebrow}</span>
303
+ {/if}
304
+ {#if title}
305
+ <h1>{title}</h1>
306
+ {/if}
307
+ {#if subtitle}
308
+ <p class="subtitle">{subtitle}</p>
309
+ {/if}
310
+ {/if}
311
+ </div>
312
+
313
+ {#if collapsible && onToggleCollapsed}
314
+ <button
315
+ class="shell-toggle"
316
+ type="button"
317
+ onclick={handleCollapseToggle}
318
+ aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
319
+ aria-expanded={!collapsed}
320
+ >
321
+ <span class="shell-toggle-glyph" aria-hidden="true">
322
+ {collapsed ? '›' : '‹'}
323
+ </span>
324
+ </button>
325
+ {/if}
326
+ </div>
327
+
328
+ {#if nav}
329
+ <nav class="nav-region" aria-label={t(M['ui.workspace_shell.workspace_navigation'])}>
330
+ {@render nav()}
331
+ </nav>
332
+ {/if}
333
+
334
+ {#if sidebarFooter}
335
+ <div class="sidebar-footer">
336
+ {@render sidebarFooter()}
337
+ </div>
338
+ {/if}
339
+ </aside>
340
+
341
+ <div class="smrt-workspace-main">
342
+ <header class="smrt-workspace-topbar">
343
+ <div class="topbar-left">
344
+ <button
345
+ class="mobile-menu"
346
+ type="button"
347
+ onclick={toggleMobileNav}
348
+ aria-label={mobileNavOpen ? 'Close navigation' : 'Open navigation'}
349
+ aria-expanded={mobileNavOpen}
350
+ >
351
+ <span aria-hidden="true">≡</span>
352
+ </button>
353
+ <div class="topbar-brand">
354
+ {#if eyebrow}
355
+ <div class="eyebrow topbar-eyebrow">{eyebrow}</div>
356
+ {/if}
357
+ {#if modeLabel}
358
+ <p class="mode-title">{modeLabel}</p>
359
+ {/if}
360
+ </div>
361
+ </div>
362
+
363
+ <div class="topbar-right">
364
+ {#if topbarActions}
365
+ <div class="topbar-actions">
366
+ {@render topbarActions()}
367
+ </div>
368
+ {/if}
369
+ {#if modeLabel || modeStatus}
370
+ <span
371
+ class="mode-badge"
372
+ data-status={modeStatus || 'default'}
373
+ role="status"
374
+ aria-live="polite"
375
+ >
376
+ <span class="mode-dot" aria-hidden="true"></span>
377
+ <span class="mode-badge-label">{modeLabel || modeStatus}</span>
378
+ </span>
379
+ {/if}
380
+ {#if modeDetail}
381
+ <p class="mode-detail">{modeDetail}</p>
382
+ {/if}
383
+ </div>
384
+ </header>
385
+
386
+ <div class="smrt-workspace-stage">
387
+ <main class="smrt-workspace-content">
388
+ {@render children()}
389
+ </main>
390
+
391
+ {#if hasInspector}
392
+ <aside
393
+ class="smrt-workspace-inspector"
394
+ aria-label={inspectorTitle}
395
+ >
396
+ <div class="inspector-header">
397
+ <span class="inspector-title">{inspectorTitle}</span>
398
+ {#if onCloseInspector}
399
+ <button
400
+ class="inspector-close"
401
+ type="button"
402
+ onclick={handleInspectorClose}
403
+ aria-label={t(M['ui.workspace_shell.close_inspector'])}
404
+ >
405
+ <span aria-hidden="true">×</span>
406
+ </button>
407
+ {/if}
408
+ </div>
409
+ <div class="inspector-body">
410
+ {@render inspector!()}
411
+ </div>
412
+ </aside>
413
+ {/if}
414
+
415
+ {#if hasInspectorRail}
416
+ <div
417
+ class="smrt-workspace-inspector-rail"
418
+ role="toolbar"
419
+ aria-label={t(M['ui.workspace_shell.inspector_tools'])}
420
+ aria-orientation="vertical"
421
+ >
422
+ {@render inspectorRail!()}
423
+ </div>
424
+ {/if}
425
+ </div>
426
+ </div>
427
+ </div>
428
+
429
+ <style>
430
+ .smrt-workspace-shell {
431
+ --smrt-ws-sidebar-width: 280px;
432
+ --smrt-ws-sidebar-collapsed-width: 96px;
433
+ --smrt-ws-inspector-width: min(420px, calc(100vw - var(--smrt-ws-sidebar-width) - 1rem));
434
+ --smrt-ws-rail-width: 3.25rem;
435
+ --smrt-ws-topbar-height: 3.75rem;
436
+ --smrt-ws-page-pad: 1.5rem;
437
+
438
+ position: relative;
439
+ display: grid;
440
+ grid-template-columns: var(--smrt-ws-sidebar-width) 1fr;
441
+ min-height: 100vh;
442
+ min-height: 100dvh;
443
+ background: var(--smrt-color-surface-container-low, #f8fafc);
444
+ color: var(--smrt-color-on-surface, #1f2937);
445
+ isolation: isolate;
446
+ }
447
+
448
+ .smrt-workspace-shell.sidebar-collapsed {
449
+ grid-template-columns: var(--smrt-ws-sidebar-collapsed-width) 1fr;
450
+ /*
451
+ * When the sidebar is collapsed, recompute the inspector-width clamp
452
+ * against the collapsed sidebar so narrower desktops with both the
453
+ * inspector and a collapsed sidebar still fit content. Without this
454
+ * override, padding-right on `.smrt-workspace-content` would still be
455
+ * sized as if the sidebar were full-width.
456
+ */
457
+ --smrt-ws-inspector-width: min(
458
+ 420px,
459
+ calc(100vw - var(--smrt-ws-sidebar-collapsed-width) - 1rem)
460
+ );
461
+ }
462
+
463
+ /* ─── Sidebar ─────────────────────────────────────────── */
464
+
465
+ .smrt-workspace-sidebar {
466
+ position: sticky;
467
+ top: 0;
468
+ align-self: start;
469
+ display: flex;
470
+ flex-direction: column;
471
+ gap: 1.25rem;
472
+ padding: 1.25rem 1rem;
473
+ height: 100vh;
474
+ height: 100dvh;
475
+ box-sizing: border-box;
476
+ background: var(--smrt-color-surface, #ffffff);
477
+ border-right: 1px solid var(--smrt-color-outline-variant, #e5e7eb);
478
+ min-width: 0;
479
+ overflow: hidden;
480
+ overscroll-behavior: contain;
481
+ z-index: 20;
482
+ }
483
+
484
+ .brand-row {
485
+ display: flex;
486
+ justify-content: space-between;
487
+ align-items: flex-start;
488
+ gap: 0.75rem;
489
+ }
490
+
491
+ .brand {
492
+ display: grid;
493
+ gap: 0.35rem;
494
+ min-width: 0;
495
+ }
496
+
497
+ .brand :global(h1),
498
+ .brand h1 {
499
+ margin: 0;
500
+ font-size: var(--smrt-typography-title-large-size, 1.25rem);
501
+ line-height: var(--smrt-typography-title-large-line-height, 1.1);
502
+ color: var(--smrt-color-on-surface, #111827);
503
+ font-weight: var(--smrt-typography-weight-semibold, 600);
504
+ }
505
+
506
+ .brand .subtitle {
507
+ margin: 0;
508
+ color: var(--smrt-color-on-surface-variant, #6b7280);
509
+ line-height: var(--smrt-typography-body-medium-line-height, 1.4);
510
+ font-size: var(--smrt-typography-body-medium-size, 0.9rem);
511
+ }
512
+
513
+ .eyebrow {
514
+ font-size: var(--smrt-typography-label-small-size, 0.7rem);
515
+ font-weight: var(--smrt-typography-weight-bold, 700);
516
+ letter-spacing: var(--smrt-typography-label-small-tracking, 0.12em);
517
+ text-transform: uppercase;
518
+ color: var(--smrt-color-on-surface-variant, #6b7280);
519
+ }
520
+
521
+ .shell-toggle {
522
+ appearance: none;
523
+ border: 1px solid var(--smrt-color-outline-variant, #e5e7eb);
524
+ background: var(--smrt-color-surface-container, #f3f4f6);
525
+ color: var(--smrt-color-on-surface, #111827);
526
+ border-radius: var(--smrt-radius-medium, 0.65rem);
527
+ width: 2rem;
528
+ height: 2rem;
529
+ display: inline-flex;
530
+ align-items: center;
531
+ justify-content: center;
532
+ cursor: pointer;
533
+ flex-shrink: 0;
534
+ transition:
535
+ background-color 150ms ease,
536
+ color 150ms ease;
537
+ }
538
+
539
+ .shell-toggle:hover {
540
+ background: var(--smrt-color-surface-container-high, #e5e7eb);
541
+ }
542
+
543
+ .shell-toggle:focus-visible {
544
+ outline: 2px solid var(--smrt-color-primary, #2563eb);
545
+ outline-offset: 2px;
546
+ }
547
+
548
+ .shell-toggle-glyph {
549
+ font-size: var(--smrt-typography-body-large-size, 1rem);
550
+ line-height: 1;
551
+ }
552
+
553
+ .nav-region {
554
+ display: flex;
555
+ flex-direction: column;
556
+ flex: 1 1 auto;
557
+ min-height: 0;
558
+ gap: 0.5rem;
559
+ overflow-y: auto;
560
+ overscroll-behavior: contain;
561
+ scrollbar-gutter: stable;
562
+ }
563
+
564
+ .sidebar-footer {
565
+ margin-top: auto;
566
+ flex: 0 0 auto;
567
+ display: grid;
568
+ gap: 0.75rem;
569
+ padding-top: 0.75rem;
570
+ background: var(--smrt-color-surface, #ffffff);
571
+ border-top: 1px solid var(--smrt-color-outline-variant, #e5e7eb);
572
+ z-index: 1;
573
+ }
574
+
575
+ /* ─── Main column ─────────────────────────────────────── */
576
+
577
+ .smrt-workspace-main {
578
+ display: flex;
579
+ flex-direction: column;
580
+ min-width: 0;
581
+ min-height: 0;
582
+ }
583
+
584
+ .smrt-workspace-topbar {
585
+ position: sticky;
586
+ top: 0;
587
+ z-index: 10;
588
+ display: flex;
589
+ align-items: center;
590
+ justify-content: space-between;
591
+ gap: 0.85rem;
592
+ padding: 0.75rem var(--smrt-ws-page-pad);
593
+ min-height: var(--smrt-ws-topbar-height);
594
+ background: var(--smrt-color-surface, #ffffff);
595
+ border-bottom: 1px solid var(--smrt-color-outline-variant, #e5e7eb);
596
+ box-shadow: var(--smrt-elevation-1, 0 1px 2px rgb(0 0 0 / 0.05));
597
+ }
598
+
599
+ .topbar-left,
600
+ .topbar-right {
601
+ display: flex;
602
+ align-items: center;
603
+ gap: 0.7rem;
604
+ min-width: 0;
605
+ }
606
+
607
+ .topbar-right {
608
+ justify-content: flex-end;
609
+ flex-wrap: wrap;
610
+ }
611
+
612
+ .topbar-actions {
613
+ display: flex;
614
+ align-items: center;
615
+ gap: 0.4rem;
616
+ flex-wrap: wrap;
617
+ }
618
+
619
+ .topbar-brand {
620
+ display: grid;
621
+ gap: 0.1rem;
622
+ min-width: 0;
623
+ }
624
+
625
+ .topbar-eyebrow {
626
+ color: var(--smrt-color-on-surface-variant, #6b7280);
627
+ }
628
+
629
+ .mode-title,
630
+ .mode-detail {
631
+ margin: 0;
632
+ }
633
+
634
+ .mode-title {
635
+ font-size: var(--smrt-typography-title-small-size, 0.9rem);
636
+ font-weight: var(--smrt-typography-weight-semibold, 600);
637
+ color: var(--smrt-color-on-surface, #111827);
638
+ }
639
+
640
+ .mode-detail {
641
+ color: var(--smrt-color-on-surface-variant, #6b7280);
642
+ max-width: 18rem;
643
+ text-align: right;
644
+ font-size: var(--smrt-typography-body-medium-size, 0.85rem);
645
+ }
646
+
647
+ .mode-badge {
648
+ display: inline-flex;
649
+ align-items: center;
650
+ gap: 0.4rem;
651
+ padding: 0.25rem 0.65rem;
652
+ border-radius: var(--smrt-radius-full, 999px);
653
+ border: 1px solid var(--smrt-color-outline-variant, #e5e7eb);
654
+ background: var(--smrt-color-surface-container, #f3f4f6);
655
+ color: var(--smrt-color-on-surface, #111827);
656
+ font-size: var(--smrt-typography-label-medium-size, 0.78rem);
657
+ font-weight: var(--smrt-typography-weight-semibold, 600);
658
+ line-height: 1;
659
+ white-space: nowrap;
660
+ }
661
+
662
+ .mode-dot {
663
+ width: 0.55rem;
664
+ height: 0.55rem;
665
+ border-radius: var(--smrt-radius-full, 9999px);
666
+ background: var(--smrt-color-on-surface-variant, #9ca3af);
667
+ flex-shrink: 0;
668
+ }
669
+
670
+ .mode-badge[data-status='active'] .mode-dot {
671
+ background: var(--smrt-color-success, #16a34a);
672
+ }
673
+ .mode-badge[data-status='offline'] .mode-dot {
674
+ background: var(--smrt-color-on-surface-variant, #9ca3af);
675
+ }
676
+ .mode-badge[data-status='local-only'] .mode-dot {
677
+ background: var(--smrt-color-primary, #2563eb);
678
+ }
679
+ .mode-badge[data-status='attention'] .mode-dot {
680
+ background: var(--smrt-color-warning, #f59e0b);
681
+ }
682
+
683
+ .mobile-menu {
684
+ display: none;
685
+ appearance: none;
686
+ border: 1px solid var(--smrt-color-outline-variant, #e5e7eb);
687
+ background: var(--smrt-color-surface, #ffffff);
688
+ color: var(--smrt-color-on-surface, #111827);
689
+ border-radius: var(--smrt-radius-medium, 0.65rem);
690
+ width: 2.35rem;
691
+ height: 2.35rem;
692
+ align-items: center;
693
+ justify-content: center;
694
+ cursor: pointer;
695
+ font-size: var(--smrt-typography-body-large-size, 1.1rem);
696
+ line-height: 1;
697
+ }
698
+
699
+ .mobile-menu:focus-visible,
700
+ .inspector-close:focus-visible {
701
+ outline: 2px solid var(--smrt-color-primary, #2563eb);
702
+ outline-offset: 2px;
703
+ }
704
+
705
+ /* ─── Stage / content / inspector ─────────────────────── */
706
+
707
+ .smrt-workspace-stage {
708
+ flex: 1;
709
+ min-height: 0;
710
+ position: relative;
711
+ }
712
+
713
+ .smrt-workspace-content {
714
+ padding: var(--smrt-ws-page-pad);
715
+ min-width: 0;
716
+ }
717
+
718
+ .smrt-workspace-inspector {
719
+ position: fixed;
720
+ top: 0;
721
+ right: 0;
722
+ bottom: 0;
723
+ width: var(--smrt-ws-inspector-width);
724
+ display: flex;
725
+ flex-direction: column;
726
+ background: var(--smrt-color-surface, #ffffff);
727
+ border-left: 1px solid var(--smrt-color-outline-variant, #e5e7eb);
728
+ box-shadow: var(--smrt-elevation-2, 0 4px 6px -1px rgb(0 0 0 / 0.1));
729
+ z-index: 22;
730
+ }
731
+
732
+ .inspector-header {
733
+ display: flex;
734
+ align-items: center;
735
+ justify-content: space-between;
736
+ padding: 0.85rem 1rem;
737
+ border-bottom: 1px solid var(--smrt-color-outline-variant, #e5e7eb);
738
+ }
739
+
740
+ .inspector-title {
741
+ font-weight: var(--smrt-typography-weight-semibold, 600);
742
+ color: var(--smrt-color-on-surface, #111827);
743
+ font-size: var(--smrt-typography-title-medium-size, 0.95rem);
744
+ }
745
+
746
+ .inspector-close {
747
+ appearance: none;
748
+ border: 1px solid transparent;
749
+ background: transparent;
750
+ color: var(--smrt-color-on-surface-variant, #6b7280);
751
+ border-radius: var(--smrt-radius-medium, 0.5rem);
752
+ width: 2rem;
753
+ height: 2rem;
754
+ display: inline-flex;
755
+ align-items: center;
756
+ justify-content: center;
757
+ font-size: var(--smrt-typography-title-large-size, 1.25rem);
758
+ line-height: 1;
759
+ cursor: pointer;
760
+ transition:
761
+ background-color 150ms ease,
762
+ color 150ms ease;
763
+ }
764
+
765
+ .inspector-close:hover {
766
+ background: var(--smrt-color-surface-container-high, #e5e7eb);
767
+ color: var(--smrt-color-on-surface, #111827);
768
+ }
769
+
770
+ .inspector-body {
771
+ flex: 1;
772
+ min-height: 0;
773
+ overflow-y: auto;
774
+ padding: 1rem;
775
+ }
776
+
777
+ .smrt-workspace-inspector-rail {
778
+ position: fixed;
779
+ top: var(--smrt-ws-topbar-height);
780
+ right: 0;
781
+ bottom: 0;
782
+ width: var(--smrt-ws-rail-width);
783
+ display: flex;
784
+ flex-direction: column;
785
+ align-items: center;
786
+ gap: 0.4rem;
787
+ padding: 0.75rem 0.25rem;
788
+ background: var(--smrt-color-surface, #ffffff);
789
+ border-left: 1px solid var(--smrt-color-outline-variant, #e5e7eb);
790
+ z-index: 18;
791
+ }
792
+
793
+ /* When both inspector + rail are present, push rail off the panel. */
794
+ .smrt-workspace-shell.has-inspector.has-inspector-rail .smrt-workspace-inspector-rail {
795
+ right: var(--smrt-ws-inspector-width);
796
+ }
797
+
798
+ /* Reserve room on the right side so content doesn't slide under panel/rail. */
799
+ @media (min-width: 961px) {
800
+ .smrt-workspace-shell.has-inspector .smrt-workspace-content,
801
+ .smrt-workspace-shell.has-inspector .smrt-workspace-topbar {
802
+ padding-right: calc(var(--smrt-ws-inspector-width) + 1rem);
803
+ }
804
+
805
+ .smrt-workspace-shell.has-inspector-rail:not(.has-inspector)
806
+ .smrt-workspace-content,
807
+ .smrt-workspace-shell.has-inspector-rail:not(.has-inspector)
808
+ .smrt-workspace-topbar {
809
+ padding-right: calc(var(--smrt-ws-rail-width) + 1rem);
810
+ }
811
+
812
+ /*
813
+ * When both the inspector panel AND the icon rail are present, the rail
814
+ * sits to the left of the panel (see `.has-inspector.has-inspector-rail
815
+ * .smrt-workspace-inspector-rail` above). Content padding must reserve
816
+ * room for both, otherwise the rail overlaps the right edge of content
817
+ * and topbar.
818
+ */
819
+ .smrt-workspace-shell.has-inspector.has-inspector-rail
820
+ .smrt-workspace-content,
821
+ .smrt-workspace-shell.has-inspector.has-inspector-rail
822
+ .smrt-workspace-topbar {
823
+ padding-right: calc(
824
+ var(--smrt-ws-inspector-width) + var(--smrt-ws-rail-width) + 1rem
825
+ );
826
+ }
827
+ }
828
+
829
+ /* ─── Backdrops (mobile only — hidden otherwise) ──────── */
830
+
831
+ /*
832
+ * Defence-in-depth: `display: none` already hides these on desktop, but a
833
+ * consumer global like `button { display: flex }` could undo that. Setting
834
+ * `pointer-events: none` ensures the backdrop can never intercept clicks
835
+ * on the desktop even if it slips back into the rendering tree.
836
+ */
837
+ .mobile-backdrop,
838
+ .inspector-backdrop {
839
+ display: none;
840
+ pointer-events: none;
841
+ }
842
+
843
+ /* ─── Tablet — collapse sidebar to icon rail ──────────── */
844
+
845
+ @media (max-width: 1240px) and (min-width: 961px) {
846
+ .smrt-workspace-shell {
847
+ grid-template-columns: var(--smrt-ws-sidebar-collapsed-width) 1fr;
848
+ }
849
+
850
+ .smrt-workspace-shell .brand-row {
851
+ justify-content: center;
852
+ }
853
+
854
+ .smrt-workspace-shell .shell-toggle {
855
+ display: none;
856
+ }
857
+
858
+ .smrt-workspace-shell .brand .subtitle,
859
+ .smrt-workspace-shell.sidebar-collapsed .brand .subtitle {
860
+ display: none;
861
+ }
862
+ }
863
+
864
+ /* ─── Mobile — drawer sidebar, drawer inspector ───────── */
865
+
866
+ @media (max-width: 960px) {
867
+ .smrt-workspace-shell,
868
+ .smrt-workspace-shell.sidebar-collapsed {
869
+ grid-template-columns: 1fr;
870
+ }
871
+
872
+ .mobile-menu {
873
+ display: inline-flex;
874
+ }
875
+
876
+ .shell-toggle {
877
+ display: none;
878
+ }
879
+
880
+ .smrt-workspace-sidebar {
881
+ position: fixed;
882
+ inset: 0 auto 0 0;
883
+ width: min(320px, calc(100vw - 3rem));
884
+ transform: translateX(-100%);
885
+ transition: transform 180ms ease;
886
+ z-index: 30;
887
+ }
888
+
889
+ .smrt-workspace-shell.nav-open .smrt-workspace-sidebar {
890
+ transform: translateX(0);
891
+ }
892
+
893
+ .mobile-backdrop {
894
+ display: block;
895
+ position: fixed;
896
+ inset: 0;
897
+ background: var(--smrt-color-scrim);
898
+ opacity: 0;
899
+ pointer-events: none;
900
+ transition: opacity 180ms ease;
901
+ border: 0;
902
+ z-index: 25;
903
+ }
904
+
905
+ .smrt-workspace-shell.nav-open .mobile-backdrop {
906
+ opacity: 1;
907
+ pointer-events: auto;
908
+ }
909
+
910
+ /* Override the inspector-width variable on mobile so the rail's right
911
+ * offset (.has-inspector.has-inspector-rail .smrt-workspace-inspector-rail
912
+ * uses var(--smrt-ws-inspector-width) for `right`) stays in sync with
913
+ * the actual rendered inspector width. */
914
+ .smrt-workspace-shell {
915
+ --smrt-ws-inspector-width: min(100vw, 420px);
916
+ }
917
+
918
+ .smrt-workspace-shell.has-inspector .smrt-workspace-inspector-rail,
919
+ .smrt-workspace-shell.has-inspector-rail:not(.has-inspector)
920
+ .smrt-workspace-inspector-rail {
921
+ top: 0;
922
+ }
923
+
924
+ .smrt-workspace-shell.has-inspector .smrt-workspace-content,
925
+ .smrt-workspace-shell.has-inspector .smrt-workspace-topbar {
926
+ padding-right: var(--smrt-ws-page-pad);
927
+ }
928
+
929
+ .smrt-workspace-shell.has-inspector-rail:not(.has-inspector)
930
+ .smrt-workspace-content,
931
+ .smrt-workspace-shell.has-inspector-rail:not(.has-inspector)
932
+ .smrt-workspace-topbar {
933
+ padding-right: calc(var(--smrt-ws-rail-width) + var(--smrt-ws-page-pad));
934
+ }
935
+
936
+ .inspector-backdrop {
937
+ display: block;
938
+ pointer-events: auto;
939
+ position: fixed;
940
+ inset: 0;
941
+ background: var(--smrt-color-scrim);
942
+ border: 0;
943
+ z-index: 21;
944
+ }
945
+
946
+ .mode-detail {
947
+ max-width: 100%;
948
+ text-align: left;
949
+ }
950
+ }
951
+ </style>