@happyvertical/smrt-svelte 0.55.6 → 0.55.8

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 (318) hide show
  1. package/AGENTS.md +9 -31
  2. package/README.md +195 -5
  3. package/agents/browser-ai.md +47 -0
  4. package/agents/command-palette.md +85 -0
  5. package/agents/import-export.md +62 -0
  6. package/agents/overview-surfaces.md +255 -0
  7. package/agents/screens.md +57 -0
  8. package/agents/workspace.md +202 -5
  9. package/dist/browser-ai/adapters/stt/dictation-source.d.ts.map +1 -1
  10. package/dist/browser-ai/adapters/stt/dictation-source.js +8 -2
  11. package/dist/browser-ai/adapters/stt/factory.d.ts +6 -3
  12. package/dist/browser-ai/adapters/stt/factory.d.ts.map +1 -1
  13. package/dist/browser-ai/adapters/stt/factory.js +19 -9
  14. package/dist/browser-ai/adapters/stt/index.d.ts +3 -1
  15. package/dist/browser-ai/adapters/stt/index.d.ts.map +1 -1
  16. package/dist/browser-ai/adapters/stt/index.js +3 -1
  17. package/dist/browser-ai/adapters/stt/local-speech-model.d.ts +111 -0
  18. package/dist/browser-ai/adapters/stt/local-speech-model.d.ts.map +1 -0
  19. package/dist/browser-ai/adapters/stt/local-speech-model.js +377 -0
  20. package/dist/browser-ai/adapters/stt/local-speech.d.ts +82 -0
  21. package/dist/browser-ai/adapters/stt/local-speech.d.ts.map +1 -0
  22. package/dist/browser-ai/adapters/stt/local-speech.js +317 -0
  23. package/dist/browser-ai/adapters/stt/local-speech.worker.d.ts +2 -0
  24. package/dist/browser-ai/adapters/stt/local-speech.worker.d.ts.map +1 -0
  25. package/dist/browser-ai/adapters/stt/local-speech.worker.js +32 -0
  26. package/dist/browser-ai/adapters/stt/transformers-env.d.ts +8 -0
  27. package/dist/browser-ai/adapters/stt/transformers-env.d.ts.map +1 -0
  28. package/dist/browser-ai/adapters/stt/transformers-env.js +17 -0
  29. package/dist/browser-ai/adapters/stt/types.d.ts +54 -9
  30. package/dist/browser-ai/adapters/stt/types.d.ts.map +1 -1
  31. package/dist/browser-ai/adapters/tts/browser-synthesis.d.ts +5 -0
  32. package/dist/browser-ai/adapters/tts/browser-synthesis.d.ts.map +1 -1
  33. package/dist/browser-ai/adapters/tts/browser-synthesis.js +83 -55
  34. package/dist/browser-ai/capabilities/speech-probe.d.ts +29 -0
  35. package/dist/browser-ai/capabilities/speech-probe.d.ts.map +1 -0
  36. package/dist/browser-ai/capabilities/speech-probe.js +19 -0
  37. package/dist/browser-ai/core/errors.d.ts +9 -0
  38. package/dist/browser-ai/core/errors.d.ts.map +1 -1
  39. package/dist/browser-ai/core/errors.js +12 -0
  40. package/dist/browser-ai/index.d.ts +1 -0
  41. package/dist/browser-ai/index.d.ts.map +1 -1
  42. package/dist/browser-ai/index.js +1 -0
  43. package/dist/components/app/AppShell.svelte +587 -33
  44. package/dist/components/app/AppShell.svelte.d.ts +75 -4
  45. package/dist/components/app/AppShell.svelte.d.ts.map +1 -1
  46. package/dist/components/app/DockToggles.svelte +0 -1
  47. package/dist/components/app/dock-toggle.d.ts +7 -1
  48. package/dist/components/app/dock-toggle.d.ts.map +1 -1
  49. package/dist/components/app/index.d.ts +2 -1
  50. package/dist/components/app/index.d.ts.map +1 -1
  51. package/dist/components/app/slot-item.d.ts +21 -0
  52. package/dist/components/app/slot-item.d.ts.map +1 -0
  53. package/dist/components/app/slot-item.js +1 -0
  54. package/dist/components/command-palette/CommandPalette.svelte +75 -0
  55. package/dist/components/command-palette/CommandPalette.svelte.d.ts +22 -0
  56. package/dist/components/command-palette/CommandPalette.svelte.d.ts.map +1 -0
  57. package/dist/components/command-palette/CommandPaletteDialog.svelte +406 -0
  58. package/dist/components/command-palette/CommandPaletteDialog.svelte.d.ts +18 -0
  59. package/dist/components/command-palette/CommandPaletteDialog.svelte.d.ts.map +1 -0
  60. package/dist/components/command-palette/CommandPaletteTrigger.svelte +91 -0
  61. package/dist/components/command-palette/CommandPaletteTrigger.svelte.d.ts +17 -0
  62. package/dist/components/command-palette/CommandPaletteTrigger.svelte.d.ts.map +1 -0
  63. package/dist/components/command-palette/context.d.ts +18 -0
  64. package/dist/components/command-palette/context.d.ts.map +1 -0
  65. package/dist/components/command-palette/context.js +38 -0
  66. package/dist/components/command-palette/controller.svelte.d.ts +70 -0
  67. package/dist/components/command-palette/controller.svelte.d.ts.map +1 -0
  68. package/dist/components/command-palette/controller.svelte.js +379 -0
  69. package/dist/components/command-palette/hotkeys.d.ts +30 -0
  70. package/dist/components/command-palette/hotkeys.d.ts.map +1 -0
  71. package/dist/components/command-palette/hotkeys.js +161 -0
  72. package/dist/components/command-palette/index.d.ts +19 -0
  73. package/dist/components/command-palette/index.d.ts.map +1 -0
  74. package/dist/components/command-palette/index.js +17 -0
  75. package/dist/components/command-palette/match.d.ts +38 -0
  76. package/dist/components/command-palette/match.d.ts.map +1 -0
  77. package/dist/components/command-palette/match.js +172 -0
  78. package/dist/components/command-palette/providers.d.ts +159 -0
  79. package/dist/components/command-palette/providers.d.ts.map +1 -0
  80. package/dist/components/command-palette/providers.js +345 -0
  81. package/dist/components/command-palette/rank.d.ts +20 -0
  82. package/dist/components/command-palette/rank.d.ts.map +1 -0
  83. package/dist/components/command-palette/rank.js +74 -0
  84. package/dist/components/command-palette/types.d.ts +119 -0
  85. package/dist/components/command-palette/types.d.ts.map +1 -0
  86. package/dist/components/command-palette/types.js +10 -0
  87. package/dist/components/command-palette/use-provider.svelte.d.ts +14 -0
  88. package/dist/components/command-palette/use-provider.svelte.d.ts.map +1 -0
  89. package/dist/components/command-palette/use-provider.svelte.js +23 -0
  90. package/dist/components/forms/AddressInput.svelte +2 -15
  91. package/dist/components/forms/AddressInput.svelte.d.ts.map +1 -1
  92. package/dist/components/forms/DateRangeInput.svelte +2 -15
  93. package/dist/components/forms/DateRangeInput.svelte.d.ts.map +1 -1
  94. package/dist/components/forms/DateTimeInput.svelte +8 -15
  95. package/dist/components/forms/DateTimeInput.svelte.d.ts.map +1 -1
  96. package/dist/components/forms/MeasurementInput.svelte +2 -15
  97. package/dist/components/forms/MeasurementInput.svelte.d.ts.map +1 -1
  98. package/dist/components/forms/MoneyInput.svelte +8 -25
  99. package/dist/components/forms/MoneyInput.svelte.d.ts.map +1 -1
  100. package/dist/components/forms/NumberInput.svelte +7 -15
  101. package/dist/components/forms/NumberInput.svelte.d.ts.map +1 -1
  102. package/dist/components/forms/PhoneInput.svelte +7 -15
  103. package/dist/components/forms/PhoneInput.svelte.d.ts.map +1 -1
  104. package/dist/components/forms/SelectInput.svelte +34 -40
  105. package/dist/components/forms/SelectInput.svelte.d.ts.map +1 -1
  106. package/dist/components/forms/TextInput.svelte +20 -37
  107. package/dist/components/forms/TextInput.svelte.d.ts.map +1 -1
  108. package/dist/components/forms/TextareaInput.svelte +19 -35
  109. package/dist/components/forms/TextareaInput.svelte.d.ts.map +1 -1
  110. package/dist/components/import-export/ImportExport.svelte +678 -0
  111. package/dist/components/import-export/ImportExport.svelte.d.ts +5 -0
  112. package/dist/components/import-export/ImportExport.svelte.d.ts.map +1 -0
  113. package/dist/components/import-export/collection-adapter.d.ts +35 -0
  114. package/dist/components/import-export/collection-adapter.d.ts.map +1 -0
  115. package/dist/components/import-export/collection-adapter.js +42 -0
  116. package/dist/components/import-export/csv.d.ts +60 -0
  117. package/dist/components/import-export/csv.d.ts.map +1 -0
  118. package/dist/components/import-export/csv.js +192 -0
  119. package/dist/components/import-export/download.d.ts +4 -0
  120. package/dist/components/import-export/download.d.ts.map +1 -0
  121. package/dist/components/import-export/download.js +17 -0
  122. package/dist/components/import-export/export.d.ts +25 -0
  123. package/dist/components/import-export/export.d.ts.map +1 -0
  124. package/dist/components/import-export/export.js +68 -0
  125. package/dist/components/import-export/fields.d.ts +48 -0
  126. package/dist/components/import-export/fields.d.ts.map +1 -0
  127. package/dist/components/import-export/fields.js +103 -0
  128. package/dist/components/import-export/index.d.ts +12 -0
  129. package/dist/components/import-export/index.d.ts.map +1 -0
  130. package/dist/components/import-export/index.js +9 -0
  131. package/dist/components/import-export/mapping.d.ts +12 -0
  132. package/dist/components/import-export/mapping.d.ts.map +1 -0
  133. package/dist/components/import-export/mapping.js +38 -0
  134. package/dist/components/import-export/props.d.ts +34 -0
  135. package/dist/components/import-export/props.d.ts.map +1 -0
  136. package/dist/components/import-export/props.js +1 -0
  137. package/dist/components/import-export/report.d.ts +13 -0
  138. package/dist/components/import-export/report.d.ts.map +1 -0
  139. package/dist/components/import-export/report.js +50 -0
  140. package/dist/components/import-export/runner.d.ts +17 -0
  141. package/dist/components/import-export/runner.d.ts.map +1 -0
  142. package/dist/components/import-export/runner.js +39 -0
  143. package/dist/components/import-export/types.d.ts +100 -0
  144. package/dist/components/import-export/types.d.ts.map +1 -0
  145. package/dist/components/import-export/types.js +6 -0
  146. package/dist/components/import-export/validate.d.ts +39 -0
  147. package/dist/components/import-export/validate.d.ts.map +1 -0
  148. package/dist/components/import-export/validate.js +268 -0
  149. package/dist/components/overview/OverviewGrid.svelte +579 -0
  150. package/dist/components/overview/OverviewGrid.svelte.d.ts +5 -0
  151. package/dist/components/overview/OverviewGrid.svelte.d.ts.map +1 -0
  152. package/dist/components/overview/WidgetOptionsForm.svelte +193 -0
  153. package/dist/components/overview/WidgetOptionsForm.svelte.d.ts +5 -0
  154. package/dist/components/overview/WidgetOptionsForm.svelte.d.ts.map +1 -0
  155. package/dist/components/overview/controller.svelte.d.ts +92 -0
  156. package/dist/components/overview/controller.svelte.d.ts.map +1 -0
  157. package/dist/components/overview/controller.svelte.js +336 -0
  158. package/dist/components/overview/format.d.ts +13 -0
  159. package/dist/components/overview/format.d.ts.map +1 -0
  160. package/dist/components/overview/format.js +45 -0
  161. package/dist/components/overview/grid-types.d.ts +52 -0
  162. package/dist/components/overview/grid-types.d.ts.map +1 -0
  163. package/dist/components/overview/grid-types.js +1 -0
  164. package/dist/components/overview/index.d.ts +24 -0
  165. package/dist/components/overview/index.d.ts.map +1 -0
  166. package/dist/components/overview/index.js +22 -0
  167. package/dist/components/overview/load.d.ts +30 -0
  168. package/dist/components/overview/load.d.ts.map +1 -0
  169. package/dist/components/overview/load.js +92 -0
  170. package/dist/components/overview/markdown.d.ts +53 -0
  171. package/dist/components/overview/markdown.d.ts.map +1 -0
  172. package/dist/components/overview/markdown.js +203 -0
  173. package/dist/components/overview/model.d.ts +100 -0
  174. package/dist/components/overview/model.d.ts.map +1 -0
  175. package/dist/components/overview/model.js +460 -0
  176. package/dist/components/overview/recipe-widgets.d.ts +77 -0
  177. package/dist/components/overview/recipe-widgets.d.ts.map +1 -0
  178. package/dist/components/overview/recipe-widgets.js +151 -0
  179. package/dist/components/overview/registry.d.ts +45 -0
  180. package/dist/components/overview/registry.d.ts.map +1 -0
  181. package/dist/components/overview/registry.js +112 -0
  182. package/dist/components/overview/resize.d.ts +25 -0
  183. package/dist/components/overview/resize.d.ts.map +1 -0
  184. package/dist/components/overview/resize.js +41 -0
  185. package/dist/components/overview/schema.d.ts +34 -0
  186. package/dist/components/overview/schema.d.ts.map +1 -0
  187. package/dist/components/overview/schema.js +180 -0
  188. package/dist/components/overview/server.d.ts +14 -0
  189. package/dist/components/overview/server.d.ts.map +1 -0
  190. package/dist/components/overview/server.js +13 -0
  191. package/dist/components/overview/types.d.ts +235 -0
  192. package/dist/components/overview/types.d.ts.map +1 -0
  193. package/dist/components/overview/types.js +17 -0
  194. package/dist/components/overview/widgets/ChartWidget.svelte +111 -0
  195. package/dist/components/overview/widgets/ChartWidget.svelte.d.ts +6 -0
  196. package/dist/components/overview/widgets/ChartWidget.svelte.d.ts.map +1 -0
  197. package/dist/components/overview/widgets/MetricWidget.svelte +86 -0
  198. package/dist/components/overview/widgets/MetricWidget.svelte.d.ts +6 -0
  199. package/dist/components/overview/widgets/MetricWidget.svelte.d.ts.map +1 -0
  200. package/dist/components/overview/widgets/NoteWidget.svelte +61 -0
  201. package/dist/components/overview/widgets/NoteWidget.svelte.d.ts +5 -0
  202. package/dist/components/overview/widgets/NoteWidget.svelte.d.ts.map +1 -0
  203. package/dist/components/overview/widgets/RecordListWidget.svelte +91 -0
  204. package/dist/components/overview/widgets/RecordListWidget.svelte.d.ts +6 -0
  205. package/dist/components/overview/widgets/RecordListWidget.svelte.d.ts.map +1 -0
  206. package/dist/components/overview/widgets/ShortcutsWidget.svelte +80 -0
  207. package/dist/components/overview/widgets/ShortcutsWidget.svelte.d.ts +6 -0
  208. package/dist/components/overview/widgets/ShortcutsWidget.svelte.d.ts.map +1 -0
  209. package/dist/components/overview/widgets/core.d.ts +44 -0
  210. package/dist/components/overview/widgets/core.d.ts.map +1 -0
  211. package/dist/components/overview/widgets/core.js +222 -0
  212. package/dist/components/overview/widgets/data.d.ts +76 -0
  213. package/dist/components/overview/widgets/data.d.ts.map +1 -0
  214. package/dist/components/overview/widgets/data.js +40 -0
  215. package/dist/components/screens/DetailScreen.svelte +220 -0
  216. package/dist/components/screens/DetailScreen.svelte.d.ts +5 -0
  217. package/dist/components/screens/DetailScreen.svelte.d.ts.map +1 -0
  218. package/dist/components/screens/EditForm.svelte +313 -0
  219. package/dist/components/screens/EditForm.svelte.d.ts +5 -0
  220. package/dist/components/screens/EditForm.svelte.d.ts.map +1 -0
  221. package/dist/components/screens/ListScreen.svelte +191 -0
  222. package/dist/components/screens/ListScreen.svelte.d.ts +5 -0
  223. package/dist/components/screens/ListScreen.svelte.d.ts.map +1 -0
  224. package/dist/components/screens/RecipeScreens.svelte +237 -0
  225. package/dist/components/screens/RecipeScreens.svelte.d.ts +5 -0
  226. package/dist/components/screens/RecipeScreens.svelte.d.ts.map +1 -0
  227. package/dist/components/screens/fields.d.ts +54 -0
  228. package/dist/components/screens/fields.d.ts.map +1 -0
  229. package/dist/components/screens/fields.js +229 -0
  230. package/dist/components/screens/index.d.ts +8 -0
  231. package/dist/components/screens/index.d.ts.map +1 -0
  232. package/dist/components/screens/index.js +6 -0
  233. package/dist/components/screens/types.d.ts +215 -0
  234. package/dist/components/screens/types.d.ts.map +1 -0
  235. package/dist/components/screens/types.js +1 -0
  236. package/dist/components/screens/values.d.ts +62 -0
  237. package/dist/components/screens/values.d.ts.map +1 -0
  238. package/dist/components/screens/values.js +289 -0
  239. package/dist/components/sortable/Sortable.svelte +6 -33
  240. package/dist/components/sortable/Sortable.svelte.d.ts.map +1 -1
  241. package/dist/components/sortable/announce.d.ts +5 -0
  242. package/dist/components/sortable/announce.d.ts.map +1 -0
  243. package/dist/components/sortable/announce.js +33 -0
  244. package/dist/components/sortable/controller.svelte.d.ts +10 -0
  245. package/dist/components/sortable/controller.svelte.d.ts.map +1 -1
  246. package/dist/components/sortable/controller.svelte.js +29 -7
  247. package/dist/components/workspace/admin-shell/AdminShell.svelte +424 -58
  248. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +27 -2
  249. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
  250. package/dist/components/workspace/admin-shell/ShellBrand.svelte +86 -0
  251. package/dist/components/workspace/admin-shell/ShellBrand.svelte.d.ts +18 -0
  252. package/dist/components/workspace/admin-shell/ShellBrand.svelte.d.ts.map +1 -0
  253. package/dist/components/workspace/admin-shell/ShellIconButton.svelte +77 -0
  254. package/dist/components/workspace/admin-shell/ShellIconButton.svelte.d.ts +26 -0
  255. package/dist/components/workspace/admin-shell/ShellIconButton.svelte.d.ts.map +1 -0
  256. package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte +88 -16
  257. package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte.d.ts.map +1 -1
  258. package/dist/components/workspace/admin-shell/ShellNavEditor.svelte +531 -0
  259. package/dist/components/workspace/admin-shell/ShellNavEditor.svelte.d.ts +26 -0
  260. package/dist/components/workspace/admin-shell/ShellNavEditor.svelte.d.ts.map +1 -0
  261. package/dist/components/workspace/admin-shell/ShellSectionIcon.svelte +57 -0
  262. package/dist/components/workspace/admin-shell/ShellSectionIcon.svelte.d.ts +22 -0
  263. package/dist/components/workspace/admin-shell/ShellSectionIcon.svelte.d.ts.map +1 -0
  264. package/dist/components/workspace/admin-shell/ShellSectionMenu.svelte +290 -0
  265. package/dist/components/workspace/admin-shell/ShellSectionMenu.svelte.d.ts +32 -0
  266. package/dist/components/workspace/admin-shell/ShellSectionMenu.svelte.d.ts.map +1 -0
  267. package/dist/components/workspace/admin-shell/TenantNav.svelte +67 -2
  268. package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts +8 -0
  269. package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
  270. package/dist/components/workspace/admin-shell/layout-controller.svelte.d.ts +67 -0
  271. package/dist/components/workspace/admin-shell/layout-controller.svelte.d.ts.map +1 -1
  272. package/dist/components/workspace/admin-shell/layout-controller.svelte.js +129 -1
  273. package/dist/components/workspace/admin-shell/layout.d.ts +122 -0
  274. package/dist/components/workspace/admin-shell/layout.d.ts.map +1 -1
  275. package/dist/components/workspace/admin-shell/layout.js +410 -10
  276. package/dist/components/workspace/admin-shell/settings.js +4 -4
  277. package/dist/components/workspace/admin-shell/shell-icons.d.ts +45 -0
  278. package/dist/components/workspace/admin-shell/shell-icons.d.ts.map +1 -0
  279. package/dist/components/workspace/admin-shell/shell-icons.js +66 -0
  280. package/dist/components/workspace/admin-shell/slot-labels.d.ts +33 -0
  281. package/dist/components/workspace/admin-shell/slot-labels.d.ts.map +1 -0
  282. package/dist/components/workspace/admin-shell/slot-labels.js +33 -0
  283. package/dist/components/workspace/admin-shell/slots.d.ts +50 -0
  284. package/dist/components/workspace/admin-shell/slots.d.ts.map +1 -0
  285. package/dist/components/workspace/admin-shell/slots.js +77 -0
  286. package/dist/components/workspace/admin-shell/state.svelte.d.ts +35 -0
  287. package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
  288. package/dist/components/workspace/admin-shell/state.svelte.js +111 -0
  289. package/dist/components/workspace/admin-shell/types.d.ts +101 -1
  290. package/dist/components/workspace/admin-shell/types.d.ts.map +1 -1
  291. package/dist/components/workspace/index.d.ts +9 -2
  292. package/dist/components/workspace/index.d.ts.map +1 -1
  293. package/dist/components/workspace/index.js +6 -0
  294. package/dist/components/workspace/manifest-nav.d.ts +20 -0
  295. package/dist/components/workspace/manifest-nav.d.ts.map +1 -1
  296. package/dist/components/workspace/manifest-nav.js +42 -13
  297. package/dist/i18n/server.d.ts +6 -0
  298. package/dist/i18n/server.d.ts.map +1 -1
  299. package/dist/i18n/server.js +6 -0
  300. package/dist/i18n/strings.import-export.d.ts +88 -0
  301. package/dist/i18n/strings.import-export.d.ts.map +1 -0
  302. package/dist/i18n/strings.import-export.js +88 -0
  303. package/dist/i18n/strings.overview.d.ts +100 -0
  304. package/dist/i18n/strings.overview.d.ts.map +1 -0
  305. package/dist/i18n/strings.overview.js +107 -0
  306. package/dist/i18n/strings.palette.d.ts +18 -0
  307. package/dist/i18n/strings.palette.d.ts.map +1 -0
  308. package/dist/i18n/strings.palette.js +18 -0
  309. package/dist/i18n/strings.screens.d.ts +31 -0
  310. package/dist/i18n/strings.screens.d.ts.map +1 -0
  311. package/dist/i18n/strings.screens.js +31 -0
  312. package/dist/i18n/strings.workspace.d.ts +49 -0
  313. package/dist/i18n/strings.workspace.d.ts.map +1 -1
  314. package/dist/i18n/strings.workspace.js +49 -0
  315. package/package.json +59 -19
  316. package/dist/browser-ai/adapters/stt/whisper-wasm.d.ts +0 -51
  317. package/dist/browser-ai/adapters/stt/whisper-wasm.d.ts.map +0 -1
  318. package/dist/browser-ai/adapters/stt/whisper-wasm.js +0 -380
@@ -9,40 +9,71 @@ import '@happyvertical/smrt-ui/themes/styles/base.css';
9
9
  import '@happyvertical/smrt-ui/themes/styles/fonts.css';
10
10
  import type { DataSurfaceRegistry } from '@happyvertical/smrt-ui/data';
11
11
  import { useI18n } from '@happyvertical/smrt-ui/i18n';
12
- import { type Snippet, untrack } from 'svelte';
12
+ import { type Component, type Snippet, tick, untrack } from 'svelte';
13
+ import { M as SORTABLE_STRINGS } from '../../i18n/strings.sortable.js';
13
14
  import { M } from '../../i18n/strings.workspace.js';
14
15
  import Provider from '../../Provider.svelte';
15
16
  import type { User } from '../../state/app-state.js';
16
17
  import type { WebMcpProviderConfig } from '../../web/webmcp-provider.js';
18
+ import { formatSortableAnnouncement } from '../sortable/announce.js';
19
+ import { createSortable } from '../sortable/controller.svelte.js';
17
20
  import AdminShell from '../workspace/admin-shell/AdminShell.svelte';
18
21
  import AppScopePanel from '../workspace/admin-shell/AppScopePanel.svelte';
19
22
  import {
20
23
  applyShellLayout,
21
24
  normalizeShellLayout,
25
+ resolveShellPlacements,
22
26
  type ShellLayout,
23
27
  } from '../workspace/admin-shell/layout.js';
24
28
  import { setShellLayout } from '../workspace/admin-shell/layout-context.js';
25
29
  import { ShellLayoutController } from '../workspace/admin-shell/layout-controller.svelte.js';
30
+ import ShellBrand from '../workspace/admin-shell/ShellBrand.svelte';
31
+ import ShellIconButton from '../workspace/admin-shell/ShellIconButton.svelte';
32
+ import ShellNavEditor from '../workspace/admin-shell/ShellNavEditor.svelte';
26
33
  import { resolveShellConfig } from '../workspace/admin-shell/settings.js';
34
+ import {
35
+ SHELL_REGION_MESSAGES,
36
+ SHELL_SLOT_MESSAGES,
37
+ SHELL_SLOT_SHORT_MESSAGES,
38
+ } from '../workspace/admin-shell/slot-labels.js';
39
+ import {
40
+ resolveSlot,
41
+ SHELL_SLOTS,
42
+ type ShellPlacementItem,
43
+ type ShellRegion,
44
+ type ShellSlot,
45
+ shellDockItemId,
46
+ shellHostSlotItemId,
47
+ slotRegion,
48
+ } from '../workspace/admin-shell/slots.js';
27
49
  import { createShellState } from '../workspace/admin-shell/state.svelte.js';
28
50
  import TenantNav from '../workspace/admin-shell/TenantNav.svelte';
29
51
  import {
52
+ type AdminShellProps,
30
53
  PANEL_EDGES,
31
54
  type PanelEdge,
32
55
  type ShellNavGroup,
33
56
  type ShellNavItem,
34
57
  type ShellPanelDefaults,
58
+ type ShellSectionActionsContext,
35
59
  } from '../workspace/admin-shell/types.js';
36
60
  import DockSlot from './DockSlot.svelte';
37
61
  import DockToggles from './DockToggles.svelte';
38
62
  import type { DockToggle } from './dock-toggle.js';
39
63
  import RuntimeDiagnosticsWebMcp from './RuntimeDiagnosticsWebMcp.svelte';
64
+ import type { ShellSlotItem } from './slot-item.js';
40
65
 
41
66
  interface Props {
42
67
  /** Application name shown in the shell brand and app panel. */
43
68
  title?: string;
44
69
  /** Secondary line under the application name in the shell brand. */
45
70
  subtitle?: string;
71
+ /** Logo shown before the title in the shell brand (and as the compact rail mark). */
72
+ logoSrc?: string;
73
+ /** Alternative text for the logo (decorative by default: the title names the app). */
74
+ logoAlt?: string;
75
+ /** Where the brand links to (e.g. the app's home); unlinked when omitted. */
76
+ homeHref?: string;
46
77
  /** localStorage key for the user's shell layout preferences. */
47
78
  storageKey?: string;
48
79
  /** Navigation rendered in the tenant (left) panel. */
@@ -94,7 +125,7 @@ interface Props {
94
125
  */
95
126
  dock?: Snippet<[DataSurfaceRegistry]>;
96
127
  /**
97
- * Icon buttons rendered at the right of the shell header, each toggling
128
+ * Icon buttons, each placed in a shell `slot` (default `header.end`), each toggling
98
129
  * the `ShellDockTool` with the same id open and closed. `aria-pressed` and
99
130
  * `aria-expanded` follow the dock, focus moves into the dock when it
100
131
  * opens and back to the button when it closes (Escape included). The
@@ -102,12 +133,72 @@ interface Props {
102
133
  * drive the same dock from code with `useShellDock()`.
103
134
  */
104
135
  dockToggles?: DockToggle[];
136
+ /**
137
+ * Host content for any shell slot (`header.start|center|end`,
138
+ * `footer.start|center|end`, `leftSidebar.header|footer`,
139
+ * `rightSidebar.header|footer`). Rendered before dock toggles placed in the
140
+ * same slot. Each snippet is one movable item with id `slot:<slot>`; use
141
+ * `slotItems` for individually movable content.
142
+ */
143
+ slots?: Partial<Record<ShellSlot, Snippet>>;
144
+ /**
145
+ * Host items with stable ids, each in a default slot. Users can move them
146
+ * between slots in the layout editor (`ShellLayout.placements`). Rendered
147
+ * after `slots` content and before dock toggles in the same slot.
148
+ */
149
+ slotItems?: ShellSlotItem[];
150
+ /**
151
+ * Opt in to editing the layout in place. Adds the built-in shell item
152
+ * `item:layout-edit` (a pencil toggle, default slot `header.end`, or
153
+ * `{ slot }`); while on, slots render as labelled drop zones, items and
154
+ * navigation get grips, section headings get overlay icons and a floating
155
+ * toolbar, and hidden regions render as strips with a show control. Hosts
156
+ * and assistants drive it with `useShellLayout()` (`editing`,
157
+ * `setEditing`). Off (the default) leaves existing apps unchanged.
158
+ *
159
+ * `{ floating: true }` instead renders the toggle as a fixed round button
160
+ * in the top-right corner of the shell: not a placeable item (no slot, no
161
+ * grip), so hidden regions never displace it. The header and right sidebar
162
+ * reserve room for it so it covers none of their controls.
163
+ */
164
+ layoutEditing?: boolean | { slot?: ShellSlot; floating?: boolean };
165
+ /**
166
+ * Host icon buttons for each navigation section while the layout is edited
167
+ * (e.g. an Options gear or Help), rendered in the section's overlay icons
168
+ * and its floating toolbar. Receives the section id, label, whether it is
169
+ * user-created, and `editing`.
170
+ */
171
+ sectionActions?: Snippet<[ShellSectionActionsContext]>;
172
+ /**
173
+ * `'items'` (default) lists every section's entries in the left sidebar.
174
+ * `'sections'` lists only the sections, one icon link each, to the
175
+ * section's own page (`ShellNavGroup.href`, else `sectionHref`, else its
176
+ * first entry); render the entries there with `ShellSectionMenu`. In edit
177
+ * mode the sidebar edits sections (grip, rename, hide, icon); entries are
178
+ * edited on the section page.
179
+ */
180
+ navMode?: 'items' | 'sections';
181
+ /** Section page for groups without an `href` (e.g. user-created ones). */
182
+ sectionHref?: (sectionId: string) => string | undefined;
183
+ /** Renders host icon names for sections and entries. */
184
+ iconComponent?: Component<{ name: string; size?: number }>;
185
+ /**
186
+ * Edge toggle buttons and WASD drop-down panels (see `AdminShell`).
187
+ * Default `false`: regions are laid out inline and no toggles or hotkeys
188
+ * exist. Pass `true` (or a per-edge map) for the previous behaviour.
189
+ */
190
+ edgeToggles?: AdminShellProps['edgeToggles'];
191
+ /** Keyboard shortcuts; default follows `edgeToggles` (see `AdminShell`). */
192
+ hotkeys?: boolean;
105
193
  children: Snippet;
106
194
  }
107
195
 
108
196
  let {
109
197
  title = 'SMRT',
110
198
  subtitle,
199
+ logoSrc,
200
+ logoAlt,
201
+ homeHref,
111
202
  storageKey = 'smrt-app-shell',
112
203
  nav = [],
113
204
  navGroups = [],
@@ -127,10 +218,84 @@ let {
127
218
  appPanelDocs,
128
219
  dock,
129
220
  dockToggles = [],
221
+ slots: hostSlots,
222
+ slotItems = [],
223
+ layoutEditing = false,
224
+ sectionActions,
225
+ navMode = 'items',
226
+ sectionHref,
227
+ iconComponent,
228
+ edgeToggles = false,
229
+ hotkeys,
130
230
  children,
131
231
  }: Props = $props();
232
+ /** Stable id of the built-in brand item (title, subtitle). */
233
+ const SHELL_BRAND_ITEM_ID = 'item:brand';
234
+ /** Stable id of the built-in edit-layout toggle item. */
235
+ const layoutFloating = $derived(
236
+ typeof layoutEditing === 'object' && layoutEditing.floating === true,
237
+ );
238
+ const SHELL_LAYOUT_EDIT_ITEM_ID = 'item:layout-edit';
132
239
  const { t } = useI18n();
133
-
240
+ // Every movable item, in default order: legacy `slots` snippets, `slotItems`,
241
+ // then dock toggles. Ids are stable (`slot:<slot>`, the host's id,
242
+ // `dock:<tool>`); the first of a duplicated id wins.
243
+ interface PlacedEntry extends ShellPlacementItem {
244
+ render?: Snippet;
245
+ toggle?: DockToggle;
246
+ /** Stays interactive and is not draggable while the layout is edited. */
247
+ fixed?: boolean;
248
+ }
249
+ const entries = $derived.by(() => {
250
+ const out: PlacedEntry[] = [];
251
+ const seen = new Set<string>();
252
+ const add = (entry: PlacedEntry) => {
253
+ if (seen.has(entry.id)) return;
254
+ seen.add(entry.id);
255
+ out.push(entry);
256
+ };
257
+ add({
258
+ id: SHELL_BRAND_ITEM_ID,
259
+ label: t(M['ui.app_shell.brand_item']),
260
+ slot: 'header.start',
261
+ render: brandItem,
262
+ });
263
+ for (const slot of SHELL_SLOTS) {
264
+ const render = hostSlots?.[slot];
265
+ if (render) {
266
+ add({
267
+ id: shellHostSlotItemId(slot),
268
+ label: t(M['ui.app_shell.slot_content'], {
269
+ slot: t(SHELL_SLOT_MESSAGES[slot]),
270
+ }),
271
+ slot,
272
+ render,
273
+ });
274
+ }
275
+ }
276
+ for (const item of slotItems) add({ ...item });
277
+ for (const toggle of dockToggles) {
278
+ add({
279
+ id: shellDockItemId(toggle.tool),
280
+ label: toggle.label,
281
+ slot: toggle.slot ?? 'header.end',
282
+ toggle,
283
+ });
284
+ }
285
+ if (layoutEditing && !layoutFloating) {
286
+ add({
287
+ id: SHELL_LAYOUT_EDIT_ITEM_ID,
288
+ label: t(M['ui.layout_edit.toggle']),
289
+ slot:
290
+ typeof layoutEditing === 'object'
291
+ ? (layoutEditing.slot ?? 'header.end')
292
+ : 'header.end',
293
+ render: layoutToggle,
294
+ fixed: true,
295
+ });
296
+ }
297
+ return out;
298
+ });
134
299
  // The shell state lives here (not inside AdminShell) so the layout can reach
135
300
  // it: panel overrides apply to it, and the user's layout is stored in it when
136
301
  // the host does not own persistence.
@@ -155,6 +320,31 @@ const applied = $derived(
155
320
  );
156
321
  const hasNav = $derived(applied.nav.length > 0 || applied.groups.length > 0);
157
322
 
323
+ const slotIds = $derived(resolveShellPlacements(entries, effectiveLayout));
324
+ const entriesFor = (name: ShellSlot): PlacedEntry[] => {
325
+ const byId = new Map(entries.map((entry) => [entry.id, entry]));
326
+ return slotIds[name].flatMap((id) => byId.get(id) ?? []);
327
+ };
328
+ const slotSnippets: Record<ShellSlot, Snippet> = {
329
+ 'header.start': slot_0,
330
+ 'header.center': slot_1,
331
+ 'header.end': slot_2,
332
+ 'footer.start': slot_3,
333
+ 'footer.center': slot_4,
334
+ 'footer.end': slot_5,
335
+ 'leftSidebar.header': slot_6,
336
+ 'leftSidebar.footer': slot_7,
337
+ 'rightSidebar.header': slot_8,
338
+ 'rightSidebar.footer': slot_9,
339
+ };
340
+ const shellSlots = $derived.by(() => {
341
+ const out: Partial<Record<ShellSlot, Snippet>> = {};
342
+ for (const name of SHELL_SLOTS) {
343
+ if (slotIds[name].length > 0) out[name] = slotSnippets[name];
344
+ }
345
+ return out;
346
+ });
347
+
158
348
  // Starting-state edits made through the layout API, applied once the layout
159
349
  // carrying them is in force (or dropped if the host answered differently).
160
350
  const pendingStart = new Map<PanelEdge, 'collapsed' | 'expanded'>();
@@ -169,38 +359,348 @@ $effect(() => {
169
359
  pendingStart.clear();
170
360
  });
171
361
 
172
- setShellLayout(
173
- new ShellLayoutController({
174
- nav: () => nav,
175
- groups: () => navGroups,
176
- panels: () => config,
177
- layout: () => effectiveLayout,
178
- commit(next) {
179
- // A changed starting state is an explicit edit: show it now. It waits
180
- // for the layout to actually change (the host may reject the edit) and
181
- // is never done for a loaded layout (hydration, a late `layout` prop),
182
- // so loading cannot erase the user's own open/closed toggle.
183
- for (const edge of PANEL_EDGES) {
184
- const before = effectiveLayout.panels?.[edge]?.initial;
185
- const after = next.panels?.[edge]?.initial;
186
- if (before === after) continue;
187
- const state = after ?? resolveShellConfig(config).panels[edge].initial;
188
- if (state === 'collapsed' || state === 'expanded') {
189
- pendingStart.set(edge, state);
190
- }
191
- }
192
- if (!controlled) {
193
- if (onlayoutchange) localLayout = next;
194
- else shell.setLayout(next);
362
+ const layoutApi = new ShellLayoutController({
363
+ editable: () => !!layoutEditing,
364
+ nav: () => nav,
365
+ groups: () => navGroups,
366
+ panels: () => config,
367
+ layout: () => effectiveLayout,
368
+ items: () => entries.map(({ id, label, slot }) => ({ id, label, slot })),
369
+ commit(next) {
370
+ // A changed starting state is an explicit edit: show it now. It waits
371
+ // for the layout to actually change (the host may reject the edit) and
372
+ // is never done for a loaded layout (hydration, a late `layout` prop),
373
+ // so loading cannot erase the user's own open/closed toggle.
374
+ for (const edge of PANEL_EDGES) {
375
+ const before = effectiveLayout.panels?.[edge]?.initial;
376
+ const after = next.panels?.[edge]?.initial;
377
+ if (before === after) continue;
378
+ const state = after ?? resolveShellConfig(config).panels[edge].initial;
379
+ if (state === 'collapsed' || state === 'expanded') {
380
+ pendingStart.set(edge, state);
195
381
  }
196
- onlayoutchange?.(next);
197
- },
198
- }),
382
+ }
383
+ if (!controlled) {
384
+ if (onlayoutchange) localLayout = next;
385
+ else shell.setLayout(next);
386
+ }
387
+ onlayoutchange?.(next);
388
+ },
389
+ });
390
+ setShellLayout(layoutApi);
391
+
392
+ // ---- In-place layout editing -------------------------------------------
393
+ const editing = $derived(layoutApi.editing);
394
+ let shellRoot = $state<HTMLElement | undefined>();
395
+ // The floating toggle sits over the header's end (header visible), the top of
396
+ // the right sidebar (header hidden) or the top of main (neither); reserve that room so it covers no
397
+ // control. AdminShell reads these custom properties.
398
+ const floatingReserve = $derived.by(() => {
399
+ if (!layoutFloating) return undefined;
400
+ const size = 'calc(2.75rem + var(--smrt-spacing-3))';
401
+ const headerShown = shell.isRegionVisible('header');
402
+ // Header hidden: the button sits over whichever region is topmost at the
403
+ // right edge: the right sidebar when shown, otherwise the main content.
404
+ const sidebarShown = shell.isEdgeShown('right');
405
+ return {
406
+ inline: headerShown ? size : '0px',
407
+ block: !headerShown && sidebarShown ? size : '0px',
408
+ main: !headerShown && !sidebarShown ? size : '0px',
409
+ };
410
+ });
411
+ let modeMessage = $state('');
412
+ let wasEditing = false;
413
+ $effect(() => {
414
+ const now = editing;
415
+ if (now === wasEditing) return;
416
+ wasEditing = now;
417
+ modeMessage = t(
418
+ now ? M['ui.layout_edit.announce_on'] : M['ui.layout_edit.announce_off'],
419
+ );
420
+ });
421
+
422
+ /** Visual (row-major) order of the drop zones, which is also the keyboard order. */
423
+ const ZONE_ORDER: readonly ShellSlot[] = [
424
+ 'header.start',
425
+ 'header.center',
426
+ 'header.end',
427
+ 'leftSidebar.header',
428
+ 'rightSidebar.header',
429
+ 'leftSidebar.footer',
430
+ 'rightSidebar.footer',
431
+ 'footer.start',
432
+ 'footer.center',
433
+ 'footer.end',
434
+ ];
435
+ const regionVisible = (region: ShellRegion): boolean =>
436
+ shell.isRegionVisible(region);
437
+ const zones = $derived(
438
+ ZONE_ORDER.filter((slot) => regionVisible(slotRegion(slot))),
439
+ );
440
+ function zoneLabel(slot: ShellSlot): string {
441
+ return t(M['ui.layout_edit.zone'], {
442
+ region: t(SHELL_REGION_MESSAGES[slotRegion(slot)]),
443
+ slot: t(SHELL_SLOT_SHORT_MESSAGES[slot]),
444
+ });
445
+ }
446
+ // Items shown in a zone: those placed there, then those that fall back to it
447
+ // because their own region is hidden or collapsed.
448
+ function zoneEntries(zone: ShellSlot): PlacedEntry[] {
449
+ const own = entriesFor(zone);
450
+ const fallen = SHELL_SLOTS.filter(
451
+ (name) => name !== zone && resolveSlot(name, regionVisible) === zone,
452
+ ).flatMap((name) => entriesFor(name));
453
+ return [...own, ...fallen];
454
+ }
455
+ const movableIn = (zone: string): PlacedEntry[] =>
456
+ zoneEntries(zone as ShellSlot).filter((entry) => !entry.fixed);
457
+
458
+ const slotSortable = createSortable({
459
+ root: () => shellRoot,
460
+ selectors: {
461
+ container: '[data-smrt-edit-zone]',
462
+ containerKey: 'smrtEditZone',
463
+ item: '[data-smrt-edit-item]',
464
+ itemKey: 'smrtEditItem',
465
+ },
466
+ containers: () => zones.map((slot) => ({ id: slot, label: zoneLabel(slot) })),
467
+ itemIds: (zone) => movableIn(zone).map((entry) => entry.id),
468
+ itemLabel: (id) =>
469
+ entries.find((entry) => entry.id === id && !entry.fixed)?.label,
470
+ allowSameContainerReorder: () => false,
471
+ enabled: () => editing,
472
+ orientation: () => 'vertical',
473
+ announce: (announcement) => formatSortableAnnouncement(t, announcement),
474
+ focusTarget: (element) =>
475
+ element.querySelector<HTMLElement>('[data-smrt-edit-handle]'),
476
+ commit(move) {
477
+ layoutApi.placeItem(move.itemId, move.target.containerId as ShellSlot);
478
+ },
479
+ });
480
+
481
+ // Native HTML drag has no per-zone listeners (the zones belong to AdminShell):
482
+ // delegate from the shell root.
483
+ $effect(() => {
484
+ const root = shellRoot;
485
+ if (!root || !editing) return;
486
+ const over = (event: DragEvent) => {
487
+ if (slotSortable.drag) event.preventDefault();
488
+ };
489
+ const drop = (event: DragEvent) => {
490
+ if (!slotSortable.drag || !(event.target instanceof Element)) return;
491
+ const zone = event.target.closest<HTMLElement>('[data-smrt-edit-zone]');
492
+ if (zone?.dataset.smrtEditZone) {
493
+ slotSortable.dropOnContainer(event, zone.dataset.smrtEditZone);
494
+ }
495
+ };
496
+ root.addEventListener('dragover', over);
497
+ root.addEventListener('drop', drop);
498
+ return () => {
499
+ root.removeEventListener('dragover', over);
500
+ root.removeEventListener('drop', drop);
501
+ };
502
+ });
503
+
504
+ const REGION_OF_EDGE: Record<PanelEdge, ShellRegion> = {
505
+ top: 'header',
506
+ bottom: 'footer',
507
+ left: 'leftSidebar',
508
+ right: 'rightSidebar',
509
+ };
510
+ const hiddenRegions = $derived(
511
+ layoutApi.panels
512
+ .filter((panel) => panel.available && !panel.visible)
513
+ .map((panel) => REGION_OF_EDGE[panel.edge]),
199
514
  );
515
+ const editSurface = $derived({
516
+ active: editing,
517
+ highlight:
518
+ (slotSortable.drag?.target.containerId as ShellSlot | undefined) ?? null,
519
+ zone: editZone,
520
+ hiddenRegions,
521
+ strip: editStrip,
522
+ regionControl: editRegionControl,
523
+ });
524
+ const visibleRegionCount = $derived(
525
+ layoutApi.panels.filter((panel) => panel.available && panel.visible).length,
526
+ );
527
+
528
+ // Editing reveals every shown sidebar so its header/footer drop zones exist
529
+ // (a collapsed one only has a rail); leaving restores what was collapsed.
530
+ // A railless edge (`rail: false`) is left as the user has it: closed stays
531
+ // closed (AdminShell offers an indicator tab to open it while editing).
532
+ let revealedByEdit: ('left' | 'right')[] = [];
533
+ $effect(() => {
534
+ if (editing) {
535
+ untrack(() => {
536
+ if (shell.viewport === 'phone') return;
537
+ for (const edge of ['left', 'right'] as const) {
538
+ if (shell.config.panels[edge].rail === false) continue;
539
+ // Opened for now only: nothing is saved, so the user's own panel
540
+ // state survives edit mode, a reload and a page unload.
541
+ if (shell.revealPanelTemporarily(edge)) revealedByEdit.push(edge);
542
+ }
543
+ });
544
+ return;
545
+ }
546
+ untrack(() => {
547
+ for (const edge of revealedByEdit) shell.endTemporaryReveal(edge);
548
+ revealedByEdit = [];
549
+ });
550
+ });
551
+
552
+ // Escape leaves edit mode, unless it is cancelling something first: a section
553
+ // toolbar (closed by ShellNavEditor, which marks the event handled) or a
554
+ // keyboard move in progress.
555
+ $effect(() => {
556
+ if (!editing) return;
557
+ const onKey = (event: KeyboardEvent) => {
558
+ if (event.key !== 'Escape' || event.defaultPrevented) return;
559
+ if (slotSortable.drag) return;
560
+ if (shellRoot?.querySelector('[data-toolbar-open]')) return;
561
+ layoutApi.setEditing(false);
562
+ };
563
+ window.addEventListener('keydown', onKey);
564
+ return () => window.removeEventListener('keydown', onKey);
565
+ });
566
+
567
+ async function toggleEditing(): Promise<void> {
568
+ layoutApi.setEditing(!layoutApi.editing);
569
+ await tick();
570
+ }
571
+ const EDGE_OF_REGION: Record<ShellRegion, PanelEdge> = {
572
+ header: 'top',
573
+ footer: 'bottom',
574
+ leftSidebar: 'left',
575
+ rightSidebar: 'right',
576
+ };
200
577
  </script>
201
578
 
202
- {#snippet dockHeader()}
203
- <DockToggles toggles={dockToggles} />
579
+ {#snippet slotBody(name: ShellSlot)}
580
+ {#each entriesFor(name) as entry (entry.id)}
581
+ {#if entry.toggle}
582
+ <DockToggles toggles={[entry.toggle]} />
583
+ {:else if entry.render}
584
+ {@render entry.render()}
585
+ {/if}
586
+ {/each}
587
+ {/snippet}
588
+ {#snippet slot_0()}
589
+ {@render slotBody('header.start')}
590
+ {/snippet}
591
+ {#snippet slot_1()}
592
+ {@render slotBody('header.center')}
593
+ {/snippet}
594
+ {#snippet slot_2()}
595
+ {@render slotBody('header.end')}
596
+ {/snippet}
597
+ {#snippet slot_3()}
598
+ {@render slotBody('footer.start')}
599
+ {/snippet}
600
+ {#snippet slot_4()}
601
+ {@render slotBody('footer.center')}
602
+ {/snippet}
603
+ {#snippet slot_5()}
604
+ {@render slotBody('footer.end')}
605
+ {/snippet}
606
+ {#snippet slot_6()}
607
+ {@render slotBody('leftSidebar.header')}
608
+ {/snippet}
609
+ {#snippet slot_7()}
610
+ {@render slotBody('leftSidebar.footer')}
611
+ {/snippet}
612
+ {#snippet slot_8()}
613
+ {@render slotBody('rightSidebar.header')}
614
+ {/snippet}
615
+ {#snippet slot_9()}
616
+ {@render slotBody('rightSidebar.footer')}
617
+ {/snippet}
618
+
619
+ {#snippet layoutToggle()}
620
+ <span class="smrt-layout-toggle" class:smrt-layout-toggle--floating={layoutFloating} data-testid="layout-edit-toggle">
621
+ <ShellIconButton
622
+ icon="edit"
623
+ pressed={editing}
624
+ label={t(M['ui.layout_edit.toggle'])}
625
+ tooltip={t(M['ui.layout_edit.toggle'])}
626
+ onclick={toggleEditing}
627
+ />
628
+ </span>
629
+ {/snippet}
630
+
631
+ {#snippet entryBody(entry: PlacedEntry)}
632
+ {#if entry.toggle}
633
+ <DockToggles toggles={[entry.toggle]} />
634
+ {:else if entry.render}
635
+ {@render entry.render()}
636
+ {/if}
637
+ {/snippet}
638
+
639
+ {#snippet editZone(slot: ShellSlot)}
640
+ <span class="smrt-edit-zone__label">{zoneLabel(slot)}</span>
641
+ {#each zoneEntries(slot) as entry (entry.id)}
642
+ {#if entry.fixed}
643
+ {@render entryBody(entry)}
644
+ {:else}
645
+ {@const dragging = slotSortable.drag?.itemId === entry.id}
646
+ <span
647
+ class="smrt-edit-item"
648
+ class:smrt-edit-item--dragging={dragging}
649
+ data-smrt-edit-item={entry.id}
650
+ >
651
+ <!-- raw-primitive-allow: native button owns keyboard pickup and HTML drag/drop -->
652
+ <button
653
+ type="button"
654
+ class="smrt-edit-item__grip"
655
+ data-smrt-edit-handle=""
656
+ draggable={slotSortable.nativeDraggable}
657
+ aria-label={t(SORTABLE_STRINGS['ui.sortable.move'], { item: entry.label })}
658
+ title={t(SORTABLE_STRINGS['ui.sortable.move'], { item: entry.label })}
659
+ aria-pressed={dragging}
660
+ onclick={() => slotSortable.consumeClick(entry.id)}
661
+ onkeydown={(event) => slotSortable.keydown(event, entry.id)}
662
+ onblur={(event) => slotSortable.blur(event, entry.id)}
663
+ onpointerdown={(event) => slotSortable.pointerDown(event, entry.id)}
664
+ onpointermove={(event) => slotSortable.pointerMove(event)}
665
+ onpointerup={(event) => slotSortable.pointerUp(event)}
666
+ onpointercancel={(event) => slotSortable.pointerCancel(event)}
667
+ ondragstart={(event) => slotSortable.dragStart(event, entry.id)}
668
+ ondragend={() => slotSortable.dragEnd()}
669
+ ><span aria-hidden="true">⠿</span></button>
670
+ <span class="smrt-edit-item__body" inert>{@render entryBody(entry)}</span>
671
+ </span>
672
+ {/if}
673
+ {/each}
674
+ {/snippet}
675
+
676
+ {#snippet brandItem()}
677
+ <ShellBrand {title} {subtitle} {logoSrc} {logoAlt} {homeHref} />
678
+ {/snippet}
679
+
680
+ {#snippet editRegionControl(region: ShellRegion)}
681
+ {@const label = t(SHELL_REGION_MESSAGES[region])}
682
+ {@const last = visibleRegionCount <= 1}
683
+ <ShellIconButton
684
+ icon="eyeOff"
685
+ size={16}
686
+ disabled={last}
687
+ label={t(M['ui.layout_edit.hide_region'], { region: label })}
688
+ tooltip={last
689
+ ? t(M['ui.layout_edit.hide_last_region'], { region: label })
690
+ : t(M['ui.layout_edit.hide_region'], { region: label })}
691
+ onclick={() => layoutApi.setPanel(EDGE_OF_REGION[region], { visible: false })}
692
+ />
693
+ {/snippet}
694
+
695
+ {#snippet editStrip(region: ShellRegion)}
696
+ {@const label = t(SHELL_REGION_MESSAGES[region])}
697
+ <span>{t(M['ui.layout_edit.region_hidden'], { region: label })}</span>
698
+ <ShellIconButton
699
+ icon="eye"
700
+ size={16}
701
+ label={t(M['ui.layout_edit.show_region'], { region: label })}
702
+ onclick={() => layoutApi.setPanel(EDGE_OF_REGION[region], { visible: true })}
703
+ />
204
704
  {/snippet}
205
705
 
206
706
  <Provider {webmcp} {user} {permissions}>
@@ -209,9 +709,18 @@ setShellLayout(
209
709
  <AdminShell
210
710
  {title}
211
711
  {subtitle}
712
+ {logoSrc}
713
+ {logoAlt}
714
+ {homeHref}
212
715
  state={shell}
213
716
  path={currentHref}
214
- header={dockToggles.length > 0 ? dockHeader : undefined}
717
+ slots={shellSlots}
718
+ layoutEdit={editSurface}
719
+ {edgeToggles}
720
+ {hotkeys}
721
+ brandInSlot
722
+ bind:rootElement={shellRoot}
723
+ {floatingReserve}
215
724
  >
216
725
  {#snippet appPanel()}
217
726
  <AppScopePanel
@@ -229,17 +738,49 @@ setShellLayout(
229
738
  </AppScopePanel>
230
739
  {/snippet}
231
740
 
741
+ {#snippet tenantRail()}
742
+ {#if navMode === 'sections' && !editing && hasNav}
743
+ <TenantNav
744
+ collapsed
745
+ items={applied.nav}
746
+ groups={applied.groups}
747
+ {currentHref}
748
+ {navMode}
749
+ {sectionHref}
750
+ {iconComponent}
751
+ aria-label={t(M['ui.app_shell.navigation'])}
752
+ />
753
+ {/if}
754
+ {/snippet}
755
+
232
756
  {#snippet tenantPanel()}
233
- {#if hasNav}
757
+ {#if editing}
758
+ <ShellNavEditor
759
+ aria-label={t(M['ui.app_shell.navigation'])}
760
+ {sectionActions}
761
+ {navMode}
762
+ {iconComponent}
763
+ />
764
+ {:else if hasNav}
234
765
  <TenantNav
235
766
  items={applied.nav}
236
767
  groups={applied.groups}
237
768
  {currentHref}
769
+ {navMode}
770
+ {sectionHref}
771
+ {iconComponent}
238
772
  aria-label={t(M['ui.app_shell.navigation'])}
239
773
  />
240
774
  {/if}
241
775
  {/snippet}
242
776
 
777
+ <div class="smrt-layout-live" role="status" aria-live="polite">{modeMessage}</div>
778
+ {#if editing}
779
+ <div class="smrt-layout-live" aria-live="assertive" aria-atomic="true">{slotSortable.announcement}</div>
780
+ {/if}
781
+ {#if layoutFloating}
782
+ {@render layoutToggle()}
783
+ {/if}
243
784
  {#if dock}
244
785
  <DockSlot {dock} />
245
786
  {/if}
@@ -252,4 +793,17 @@ setShellLayout(
252
793
  a {
253
794
  color: var(--smrt-color-primary);
254
795
  }
796
+ .smrt-layout-toggle { display: inline-flex; align-items: center; gap: var(--smrt-spacing-1); }
797
+ .smrt-layout-toggle--floating { position: fixed; z-index: 45; inset-block-start: max(var(--smrt-spacing-3), env(safe-area-inset-top)); inset-inline-end: max(var(--smrt-spacing-3), env(safe-area-inset-right)); border-radius: var(--smrt-radius-full); background: var(--smrt-color-surface-container-high); box-shadow: var(--smrt-elevation-2); }
798
+ .smrt-layout-toggle--floating :global(.smrt-shell-icon-button) { border-radius: var(--smrt-radius-full); }
799
+ .smrt-layout-toggle :global(.smrt-shell-icon-button[aria-pressed='true']) { background: var(--smrt-color-primary-container); color: var(--smrt-color-on-primary-container); }
800
+ .smrt-layout-live { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
801
+ .smrt-edit-zone__label { flex: 0 0 auto; color: var(--smrt-color-on-surface-variant); font: var(--smrt-typography-label-small-font); white-space: nowrap; }
802
+ .smrt-edit-item { display: inline-flex; align-items: center; min-inline-size: 0; border: 1px solid var(--smrt-color-outline-variant); border-radius: var(--smrt-radius-medium); background: var(--smrt-color-surface); }
803
+ .smrt-edit-item--dragging { opacity: 0.55; }
804
+ .smrt-edit-item__body { display: inline-flex; align-items: center; min-inline-size: 0; pointer-events: none; }
805
+ .smrt-edit-item__grip { display: inline-grid; place-items: center; flex: 0 0 auto; inline-size: 1.5rem; block-size: 2rem; padding: 0; border: 0; border-radius: var(--smrt-radius-sm); background: transparent; color: var(--smrt-color-on-surface-variant); cursor: grab; touch-action: none; }
806
+ .smrt-edit-item__grip:hover { background: var(--smrt-color-surface-container-high); }
807
+ .smrt-edit-item__grip:focus-visible { outline: 2px solid var(--smrt-color-primary); outline-offset: 2px; }
808
+ .smrt-edit-item__grip[aria-pressed='true'] { background: var(--smrt-color-primary-container); color: var(--smrt-color-on-primary-container); }
255
809
  </style>