@morscherlab/mint-sdk 1.0.0-beta.2 → 1.0.0-beta.4

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 (427) hide show
  1. package/README.md +225 -6
  2. package/dist/__tests__/components/ActionItem.test.d.ts +1 -0
  3. package/dist/__tests__/components/AppAvatarMenu.test.d.ts +1 -0
  4. package/dist/__tests__/components/AppPageSelector.test.d.ts +1 -0
  5. package/dist/__tests__/components/AppPillNav.test.d.ts +1 -0
  6. package/dist/__tests__/components/AppPluginSwitcher.test.d.ts +1 -0
  7. package/dist/__tests__/components/AppToastContainer.test.d.ts +1 -0
  8. package/dist/__tests__/components/BaseRadioGroup.test.d.ts +1 -0
  9. package/dist/__tests__/components/BaseSelect.test.d.ts +1 -0
  10. package/dist/__tests__/components/BaseTabs.test.d.ts +1 -0
  11. package/dist/__tests__/components/BatchProgressList.test.d.ts +1 -0
  12. package/dist/__tests__/components/BioTemplateExperimentWorkspaceView.test.d.ts +1 -0
  13. package/dist/__tests__/components/BioTemplatePackWorkspaceView.test.d.ts +1 -0
  14. package/dist/__tests__/components/BioTemplatePresetWorkspaceView.test.d.ts +1 -0
  15. package/dist/__tests__/components/BioTemplateRenderer.test.d.ts +1 -0
  16. package/dist/__tests__/components/Breadcrumb.test.d.ts +1 -0
  17. package/dist/__tests__/components/CalendarGridPanel.test.d.ts +1 -0
  18. package/dist/__tests__/components/ComponentBindingRenderer.test.d.ts +1 -0
  19. package/dist/__tests__/components/ConcentrationInput.test.d.ts +1 -0
  20. package/dist/__tests__/components/ControlWorkspaceView.test.d.ts +1 -0
  21. package/dist/__tests__/components/DatePicker.test.d.ts +1 -0
  22. package/dist/__tests__/components/DateTimePicker.test.d.ts +1 -0
  23. package/dist/__tests__/components/DoseDesignWorkspaceView.test.d.ts +1 -0
  24. package/dist/__tests__/components/EmptyState.test.d.ts +1 -0
  25. package/dist/__tests__/components/ExperimentPopover.test.d.ts +1 -0
  26. package/dist/__tests__/components/FormBuilder.test.d.ts +1 -0
  27. package/dist/__tests__/components/GroupAssigner.test.d.ts +1 -0
  28. package/dist/__tests__/components/MultiSelect.test.d.ts +1 -0
  29. package/dist/__tests__/components/PluginWorkspaceView.test.d.ts +1 -0
  30. package/dist/__tests__/components/ProtocolStepEditor.test.d.ts +1 -0
  31. package/dist/__tests__/components/ReagentList.test.d.ts +1 -0
  32. package/dist/__tests__/components/SampleHierarchyTree.test.d.ts +1 -0
  33. package/dist/__tests__/components/SampleSelector.test.d.ts +1 -0
  34. package/dist/__tests__/components/SegmentedControl.test.d.ts +1 -0
  35. package/dist/__tests__/components/SettingsModal.test.d.ts +1 -0
  36. package/dist/__tests__/components/TagsInput.test.d.ts +1 -0
  37. package/dist/__tests__/components/ThemeToggle.test.d.ts +1 -0
  38. package/dist/__tests__/components/TimePicker.test.d.ts +1 -0
  39. package/dist/__tests__/composables/experiment-utils.test.d.ts +1 -0
  40. package/dist/__tests__/composables/useApi.test.d.ts +1 -0
  41. package/dist/__tests__/composables/useBioTemplatePackWorkspace.test.d.ts +1 -0
  42. package/dist/__tests__/composables/useBioTemplatePresetWorkspace.test.d.ts +1 -0
  43. package/dist/__tests__/composables/useBioTemplateWorkspace.test.d.ts +1 -0
  44. package/dist/__tests__/composables/useCalendarGrid.test.d.ts +1 -0
  45. package/dist/__tests__/composables/useControlSchema.test.d.ts +1 -0
  46. package/dist/__tests__/composables/useDebouncedWatch.test.d.ts +1 -0
  47. package/dist/__tests__/composables/useDropdownState.test.d.ts +1 -0
  48. package/dist/__tests__/composables/useEventListener.test.d.ts +1 -0
  49. package/dist/__tests__/composables/useExpansionSet.test.d.ts +1 -0
  50. package/dist/__tests__/composables/useExperimentData.test.d.ts +1 -0
  51. package/dist/__tests__/composables/useExperimentSelector.test.d.ts +1 -0
  52. package/dist/__tests__/composables/useGroupAssignment.test.d.ts +1 -0
  53. package/dist/__tests__/composables/useListSelection.test.d.ts +1 -0
  54. package/dist/__tests__/composables/usePluginClient.test.d.ts +1 -0
  55. package/dist/__tests__/composables/usePluginConfig.test.d.ts +1 -0
  56. package/dist/__tests__/composables/useRequestSyncState.test.d.ts +1 -0
  57. package/dist/__tests__/composables/useSampleGroups.test.d.ts +1 -0
  58. package/dist/__tests__/composables/useSelectionLimit.test.d.ts +1 -0
  59. package/dist/__tests__/composables/useSortedItems.test.d.ts +1 -0
  60. package/dist/__tests__/composables/useTemplateCollection.test.d.ts +1 -0
  61. package/dist/__tests__/composables/useTextSearch.test.d.ts +1 -0
  62. package/dist/__tests__/composables/useTheme.test.d.ts +1 -0
  63. package/dist/__tests__/composables/useTimeUtils.test.d.ts +1 -0
  64. package/dist/__tests__/docs/frontendDocsCatalog.test.d.ts +1 -0
  65. package/dist/__tests__/templates/templates.test.d.ts +1 -0
  66. package/dist/{auth-DsI0rQ7_.js → auth-QQj2kkze.js} +12 -5
  67. package/dist/auth-QQj2kkze.js.map +1 -0
  68. package/dist/components/AppAvatarMenu.vue.d.ts +2 -7
  69. package/dist/components/AppContainer.vue.d.ts +1 -1
  70. package/dist/components/AppLayout.vue.d.ts +20 -1
  71. package/dist/components/AppSidebar.vue.d.ts +111 -6
  72. package/dist/components/AppTopBar.vue.d.ts +35 -22
  73. package/dist/components/BaseButton.vue.d.ts +1 -1
  74. package/dist/components/BaseCheckbox.vue.d.ts +1 -1
  75. package/dist/components/BaseInput.vue.d.ts +2 -2
  76. package/dist/components/BasePill.vue.d.ts +2 -2
  77. package/dist/components/BaseRadioGroup.vue.d.ts +3 -3
  78. package/dist/components/BaseSelect.vue.d.ts +3 -3
  79. package/dist/components/BaseTabs.vue.d.ts +2 -2
  80. package/dist/components/BaseTextarea.vue.d.ts +1 -1
  81. package/dist/components/BaseToggle.vue.d.ts +1 -1
  82. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +119 -0
  83. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +93 -0
  84. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +87 -0
  85. package/dist/components/BioTemplateRenderer.vue.d.ts +29 -0
  86. package/dist/components/Breadcrumb.vue.d.ts +2 -2
  87. package/dist/components/Calendar.vue.d.ts +1 -1
  88. package/dist/components/CollapsibleCard.vue.d.ts +1 -1
  89. package/dist/components/ComponentBindingRenderer.vue.d.ts +44 -0
  90. package/dist/components/ConcentrationInput.vue.d.ts +2 -2
  91. package/dist/components/ConfirmDialog.vue.d.ts +2 -2
  92. package/dist/components/ControlWorkspaceView.vue.d.ts +147 -0
  93. package/dist/components/DatePicker.vue.d.ts +1 -1
  94. package/dist/components/DateTimePicker.vue.d.ts +3 -3
  95. package/dist/components/Divider.vue.d.ts +1 -1
  96. package/dist/components/DoseDesignWorkspaceView.vue.d.ts +149 -0
  97. package/dist/components/DropdownButton.vue.d.ts +3 -3
  98. package/dist/components/EmptyState.vue.d.ts +1 -2
  99. package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
  100. package/dist/components/ExperimentTimeline.vue.d.ts +2 -2
  101. package/dist/components/FileUploader.vue.d.ts +1 -1
  102. package/dist/components/FitPanel.vue.d.ts +1 -1
  103. package/dist/components/FormActions.vue.d.ts +4 -4
  104. package/dist/components/FormBuilder.vue.d.ts +31 -17
  105. package/dist/components/FormulaInput.vue.d.ts +2 -2
  106. package/dist/components/MoleculeInput.vue.d.ts +2 -2
  107. package/dist/components/MultiSelect.vue.d.ts +3 -3
  108. package/dist/components/NumberInput.vue.d.ts +1 -1
  109. package/dist/components/PlateMapEditor.vue.d.ts +1 -1
  110. package/dist/components/PluginWorkspaceView.vue.d.ts +310 -0
  111. package/dist/components/ProgressBar.vue.d.ts +1 -1
  112. package/dist/components/ProtocolStepEditor.vue.d.ts +3 -1
  113. package/dist/components/RackEditor.vue.d.ts +2 -2
  114. package/dist/components/SampleLegend.vue.d.ts +2 -2
  115. package/dist/components/ScheduleCalendar.vue.d.ts +2 -2
  116. package/dist/components/SegmentedControl.vue.d.ts +2 -2
  117. package/dist/components/SequenceInput.vue.d.ts +3 -3
  118. package/dist/components/SettingsModal.vue.d.ts +14 -6
  119. package/dist/components/StatusIndicator.vue.d.ts +1 -1
  120. package/dist/components/TagsInput.vue.d.ts +3 -2
  121. package/dist/components/TimePicker.vue.d.ts +3 -3
  122. package/dist/components/TimeRangeInput.vue.d.ts +1 -1
  123. package/dist/components/UnitInput.vue.d.ts +2 -2
  124. package/dist/components/WellPlate.vue.d.ts +6 -6
  125. package/dist/components/index.d.ts +9 -8
  126. package/dist/components/index.js +3 -3
  127. package/dist/components/{SettingsButton.vue.d.ts → internal/ActionItemInternal.vue.d.ts} +11 -9
  128. package/dist/components/{AppPageSelector.vue.d.ts → internal/AppPageSelectorInternal.vue.d.ts} +3 -6
  129. package/dist/components/{AppPillNav.vue.d.ts → internal/AppPillNavInternal.vue.d.ts} +4 -2
  130. package/dist/components/internal/CalendarGridPanelInternal.vue.d.ts +25 -0
  131. package/dist/components/{FormFieldRenderer.vue.d.ts → internal/FormFieldRendererInternal.vue.d.ts} +2 -2
  132. package/dist/components/{FormSection.vue.d.ts → internal/FormSectionRenderer.vue.d.ts} +7 -7
  133. package/dist/components/{WellEditPopup.vue.d.ts → internal/WellEditPopupInternal.vue.d.ts} +1 -1
  134. package/dist/{components-_XqPEhP9.js → components-BkGF4B4y.js} +9760 -8471
  135. package/dist/components-BkGF4B4y.js.map +1 -0
  136. package/dist/composables/experiment-utils.d.ts +8 -0
  137. package/dist/composables/index.d.ts +22 -5
  138. package/dist/composables/index.js +4 -3
  139. package/dist/composables/platformContextHelpers.d.ts +14 -0
  140. package/dist/composables/useAppExperiment.d.ts +31 -2
  141. package/dist/composables/useBioTemplateComponents.d.ts +22 -0
  142. package/dist/composables/useBioTemplateControls.d.ts +6 -0
  143. package/dist/composables/useBioTemplatePackWorkspace.d.ts +46 -0
  144. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +75 -0
  145. package/dist/composables/useBioTemplateWorkspace.d.ts +51 -0
  146. package/dist/composables/useCalendarGrid.d.ts +26 -0
  147. package/dist/composables/useControlSchema.d.ts +343 -0
  148. package/dist/composables/useDebouncedWatch.d.ts +20 -0
  149. package/dist/composables/useDropdownState.d.ts +19 -0
  150. package/dist/composables/useEventListener.d.ts +13 -0
  151. package/dist/composables/useExpansionSet.d.ts +21 -0
  152. package/dist/composables/useExperimentData.d.ts +10 -0
  153. package/dist/composables/useExperimentSave.d.ts +31 -2
  154. package/dist/composables/useExperimentSelector.d.ts +20 -0
  155. package/dist/composables/useForm.d.ts +2 -0
  156. package/dist/composables/useGroupAssignment.d.ts +31 -0
  157. package/dist/composables/useListSelection.d.ts +35 -0
  158. package/dist/composables/usePlatformContext.d.ts +21 -3
  159. package/dist/composables/usePluginClient.d.ts +112 -0
  160. package/dist/composables/usePluginConfig.d.ts +12 -0
  161. package/dist/composables/useRequestSyncState.d.ts +34 -0
  162. package/dist/composables/useSampleGroups.d.ts +32 -0
  163. package/dist/composables/useSelectionLimit.d.ts +17 -0
  164. package/dist/composables/useSortedItems.d.ts +32 -0
  165. package/dist/composables/useTemplateCollection.d.ts +58 -0
  166. package/dist/composables/useTextSearch.d.ts +18 -0
  167. package/dist/composables/useTimeUtils.d.ts +8 -0
  168. package/dist/{composables-tiZqLu1M.js → composables-CHsME9H1.js} +240 -146
  169. package/dist/composables-CHsME9H1.js.map +1 -0
  170. package/dist/index.d.ts +6 -4
  171. package/dist/index.js +6 -5
  172. package/dist/install.d.ts +7 -2
  173. package/dist/install.js +2 -2
  174. package/dist/install.js.map +1 -1
  175. package/dist/stores/index.js +1 -1
  176. package/dist/stores/settings.d.ts +4 -1
  177. package/dist/styles.css +4746 -5514
  178. package/dist/templates/adapters.d.ts +43 -0
  179. package/dist/templates/builders.d.ts +63 -0
  180. package/dist/templates/catalog.d.ts +188 -0
  181. package/dist/templates/componentBindings.d.ts +71 -0
  182. package/dist/templates/controlSchemas.d.ts +25 -0
  183. package/dist/templates/index.d.ts +15 -0
  184. package/dist/templates/index.js +2 -0
  185. package/dist/templates/lookup.d.ts +4 -0
  186. package/dist/templates/packs.d.ts +18 -0
  187. package/dist/templates/presets.d.ts +90 -0
  188. package/dist/templates/types.d.ts +531 -0
  189. package/dist/templates-B5jmTWuk.js +9388 -0
  190. package/dist/templates-B5jmTWuk.js.map +1 -0
  191. package/dist/types/components.d.ts +26 -23
  192. package/dist/types/form-builder.d.ts +6 -8
  193. package/dist/types/index.d.ts +2 -2
  194. package/dist/types/platform.d.ts +7 -1
  195. package/dist/useScheduleDrag-BgzpQT53.js +4414 -0
  196. package/dist/useScheduleDrag-BgzpQT53.js.map +1 -0
  197. package/dist/utils/formModelSync.d.ts +5 -0
  198. package/dist/utils/items.d.ts +8 -0
  199. package/dist/utils/options.d.ts +6 -0
  200. package/dist/utils/pluginIcon.d.ts +9 -0
  201. package/package.json +7 -2
  202. package/src/__tests__/components/ActionItem.test.ts +99 -0
  203. package/src/__tests__/components/AppAvatarMenu.test.ts +27 -0
  204. package/src/__tests__/components/AppLayout.test.ts +44 -0
  205. package/src/__tests__/components/AppPageSelector.test.ts +134 -0
  206. package/src/__tests__/components/AppPillNav.test.ts +125 -0
  207. package/src/__tests__/components/AppPluginSwitcher.test.ts +44 -0
  208. package/src/__tests__/components/AppSidebar.test.ts +496 -0
  209. package/src/__tests__/components/AppToastContainer.test.ts +37 -0
  210. package/src/__tests__/components/AppTopBar.test.ts +455 -9
  211. package/src/__tests__/components/BaseRadioGroup.test.ts +25 -0
  212. package/src/__tests__/components/BaseSelect.test.ts +21 -0
  213. package/src/__tests__/components/BaseTabs.test.ts +25 -0
  214. package/src/__tests__/components/BatchProgressList.test.ts +52 -0
  215. package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +159 -0
  216. package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +175 -0
  217. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +306 -0
  218. package/src/__tests__/components/BioTemplateRenderer.test.ts +71 -0
  219. package/src/__tests__/components/Breadcrumb.test.ts +23 -0
  220. package/src/__tests__/components/CalendarGridPanel.test.ts +36 -0
  221. package/src/__tests__/components/ComponentBindingRenderer.test.ts +161 -0
  222. package/src/__tests__/components/ConcentrationInput.test.ts +45 -0
  223. package/src/__tests__/components/ControlWorkspaceView.test.ts +1102 -0
  224. package/src/__tests__/components/DataFrame.test.ts +11 -0
  225. package/src/__tests__/components/DatePicker.test.ts +45 -0
  226. package/src/__tests__/components/DateTimePicker.test.ts +48 -0
  227. package/src/__tests__/components/DoseDesignWorkspaceView.test.ts +185 -0
  228. package/src/__tests__/components/DropdownButton.test.ts +23 -0
  229. package/src/__tests__/components/EmptyState.test.ts +23 -0
  230. package/src/__tests__/components/ExperimentPopover.test.ts +56 -0
  231. package/src/__tests__/components/FormBuilder.test.ts +296 -0
  232. package/src/__tests__/components/GroupAssigner.test.ts +30 -0
  233. package/src/__tests__/components/MultiSelect.test.ts +48 -0
  234. package/src/__tests__/components/PluginWorkspaceView.test.ts +548 -0
  235. package/src/__tests__/components/ProtocolStepEditor.test.ts +33 -0
  236. package/src/__tests__/components/ReagentList.test.ts +82 -0
  237. package/src/__tests__/components/SampleHierarchyTree.test.ts +53 -0
  238. package/src/__tests__/components/SampleSelector.test.ts +60 -0
  239. package/src/__tests__/components/SegmentedControl.test.ts +24 -0
  240. package/src/__tests__/components/SettingsModal.test.ts +296 -0
  241. package/src/__tests__/components/TagsInput.test.ts +75 -0
  242. package/src/__tests__/components/ThemeToggle.test.ts +47 -0
  243. package/src/__tests__/components/TimePicker.test.ts +38 -0
  244. package/src/__tests__/composables/experiment-utils.test.ts +30 -0
  245. package/src/__tests__/composables/useApi.test.ts +30 -0
  246. package/src/__tests__/composables/useAppExperiment.test.ts +100 -1
  247. package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +125 -0
  248. package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +199 -0
  249. package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +104 -0
  250. package/src/__tests__/composables/useCalendarGrid.test.ts +38 -0
  251. package/src/__tests__/composables/useControlSchema.test.ts +1033 -0
  252. package/src/__tests__/composables/useDebouncedWatch.test.ts +93 -0
  253. package/src/__tests__/composables/useDropdownState.test.ts +95 -0
  254. package/src/__tests__/composables/useEventListener.test.ts +116 -0
  255. package/src/__tests__/composables/useExpansionSet.test.ts +62 -0
  256. package/src/__tests__/composables/useExperimentData.test.ts +4 -0
  257. package/src/__tests__/composables/useExperimentSave.test.ts +203 -8
  258. package/src/__tests__/composables/useExperimentSelector.test.ts +164 -0
  259. package/src/__tests__/composables/useForm.test.ts +58 -0
  260. package/src/__tests__/composables/useFormBuilder.test.ts +77 -0
  261. package/src/__tests__/composables/useGroupAssignment.test.ts +73 -0
  262. package/src/__tests__/composables/useListSelection.test.ts +66 -0
  263. package/src/__tests__/composables/usePluginClient.test.ts +541 -0
  264. package/src/__tests__/composables/usePluginConfig.test.ts +5 -0
  265. package/src/__tests__/composables/useRequestSyncState.test.ts +92 -0
  266. package/src/__tests__/composables/useSampleGroups.test.ts +66 -0
  267. package/src/__tests__/composables/useSelectionLimit.test.ts +41 -0
  268. package/src/__tests__/composables/useSortedItems.test.ts +87 -0
  269. package/src/__tests__/composables/useTemplateCollection.test.ts +147 -0
  270. package/src/__tests__/composables/useTextSearch.test.ts +55 -0
  271. package/src/__tests__/composables/useTheme.test.ts +91 -0
  272. package/src/__tests__/composables/useTimeUtils.test.ts +35 -0
  273. package/src/__tests__/docs/frontendDocsCatalog.test.ts +324 -0
  274. package/src/__tests__/fixtures/templates/dose-response.json +81 -0
  275. package/src/__tests__/fixtures/templates/plate-map.json +54 -0
  276. package/src/__tests__/fixtures/templates/qpcr-plate.json +96 -0
  277. package/src/__tests__/fixtures/templates/sample-sheet.json +71 -0
  278. package/src/__tests__/templates/templates.test.ts +1055 -0
  279. package/src/components/AppAvatarMenu.vue +15 -69
  280. package/src/components/AppLayout.story.vue +64 -25
  281. package/src/components/AppLayout.vue +83 -2
  282. package/src/components/AppPluginSwitcher.vue +41 -145
  283. package/src/components/AppSidebar.story.vue +203 -1
  284. package/src/components/AppSidebar.vue +320 -25
  285. package/src/components/{ToastNotification.story.vue → AppToastContainer.story.vue} +6 -6
  286. package/src/components/{ToastNotification.vue → AppToastContainer.vue} +1 -1
  287. package/src/components/AppTopBar.story.vue +7 -33
  288. package/src/components/AppTopBar.vue +104 -300
  289. package/src/components/BaseModal.vue +3 -5
  290. package/src/components/BaseRadioGroup.vue +7 -3
  291. package/src/components/BaseSelect.vue +11 -7
  292. package/src/components/BaseTabs.vue +6 -4
  293. package/src/components/BatchProgressList.vue +5 -8
  294. package/src/components/BioTemplateExperimentWorkspaceView.story.vue +123 -0
  295. package/src/components/BioTemplateExperimentWorkspaceView.vue +343 -0
  296. package/src/components/BioTemplatePackWorkspaceView.story.vue +107 -0
  297. package/src/components/BioTemplatePackWorkspaceView.vue +177 -0
  298. package/src/components/BioTemplatePresetWorkspaceView.story.vue +163 -0
  299. package/src/components/BioTemplatePresetWorkspaceView.vue +401 -0
  300. package/src/components/BioTemplateRenderer.story.vue +57 -0
  301. package/src/components/BioTemplateRenderer.vue +57 -0
  302. package/src/components/Breadcrumb.vue +14 -8
  303. package/src/components/ComponentBindingRenderer.story.vue +57 -0
  304. package/src/components/ComponentBindingRenderer.vue +308 -0
  305. package/src/components/ConcentrationInput.vue +27 -64
  306. package/src/components/ControlWorkspaceView.story.vue +347 -0
  307. package/src/components/ControlWorkspaceView.vue +378 -0
  308. package/src/components/DataFrame.vue +34 -50
  309. package/src/components/DatePicker.vue +59 -192
  310. package/src/components/DateTimePicker.vue +50 -171
  311. package/src/components/DoseDesignWorkspaceView.story.vue +77 -0
  312. package/src/components/DoseDesignWorkspaceView.vue +255 -0
  313. package/src/components/DropdownButton.vue +14 -32
  314. package/src/components/EmptyState.vue +4 -2
  315. package/src/components/ExperimentPopover.vue +7 -28
  316. package/src/components/ExperimentSelectorModal.vue +6 -5
  317. package/src/components/FormBuilder.story.vue +190 -0
  318. package/src/components/FormBuilder.vue +124 -27
  319. package/src/components/GroupAssigner.vue +24 -56
  320. package/src/components/MultiSelect.vue +17 -12
  321. package/src/components/PlateMapEditor.vue +3 -8
  322. package/src/components/PluginIcon.vue +2 -22
  323. package/src/components/PluginWorkspaceView.story.vue +334 -0
  324. package/src/components/PluginWorkspaceView.vue +708 -0
  325. package/src/components/ProtocolStepEditor.vue +13 -22
  326. package/src/components/ReagentList.vue +25 -33
  327. package/src/components/SampleHierarchyTree.vue +12 -23
  328. package/src/components/SampleSelector.vue +42 -122
  329. package/src/components/SegmentedControl.vue +7 -3
  330. package/src/components/SettingsModal.story.vue +88 -1
  331. package/src/components/SettingsModal.vue +120 -29
  332. package/src/components/TagsInput.vue +29 -14
  333. package/src/components/ThemeToggle.vue +9 -7
  334. package/src/components/TimePicker.vue +19 -41
  335. package/src/components/Tooltip.vue +7 -12
  336. package/src/components/WellPlate.vue +6 -12
  337. package/src/components/index.ts +9 -8
  338. package/src/components/internal/ActionItemInternal.vue +82 -0
  339. package/src/components/internal/AppPageSelectorInternal.vue +128 -0
  340. package/src/components/internal/AppPillNavInternal.vue +194 -0
  341. package/src/components/internal/CalendarGridPanelInternal.vue +120 -0
  342. package/src/components/{FormFieldRenderer.vue → internal/FormFieldRendererInternal.vue} +4 -12
  343. package/src/components/{FormSection.vue → internal/FormSectionRenderer.vue} +6 -18
  344. package/src/components/{WellEditPopup.vue → internal/WellEditPopupInternal.vue} +5 -10
  345. package/src/composables/experiment-utils.ts +26 -0
  346. package/src/composables/index.ts +229 -3
  347. package/src/composables/platformContextHelpers.ts +74 -0
  348. package/src/composables/useApi.ts +9 -2
  349. package/src/composables/useAppExperiment.ts +85 -13
  350. package/src/composables/useBioTemplateComponents.ts +105 -0
  351. package/src/composables/useBioTemplateControls.ts +41 -0
  352. package/src/composables/useBioTemplatePackWorkspace.ts +185 -0
  353. package/src/composables/useBioTemplatePresetWorkspace.ts +326 -0
  354. package/src/composables/useBioTemplateWorkspace.ts +141 -0
  355. package/src/composables/useCalendarGrid.ts +140 -0
  356. package/src/composables/useControlSchema.ts +1362 -0
  357. package/src/composables/useDebouncedWatch.ts +119 -0
  358. package/src/composables/useDropdownState.ts +83 -0
  359. package/src/composables/useEventListener.ts +111 -0
  360. package/src/composables/useExpansionSet.ts +117 -0
  361. package/src/composables/useExperimentData.ts +20 -11
  362. package/src/composables/useExperimentSave.ts +202 -50
  363. package/src/composables/useExperimentSelector.ts +86 -72
  364. package/src/composables/useForm.ts +49 -4
  365. package/src/composables/useFormBuilder.ts +93 -42
  366. package/src/composables/useGroupAssignment.ts +148 -0
  367. package/src/composables/useListSelection.ts +158 -0
  368. package/src/composables/usePluginClient.ts +466 -0
  369. package/src/composables/usePluginConfig.ts +34 -13
  370. package/src/composables/useRequestSyncState.ts +126 -0
  371. package/src/composables/useSampleGroups.ts +126 -0
  372. package/src/composables/useSelectionLimit.ts +57 -0
  373. package/src/composables/useSortedItems.ts +118 -0
  374. package/src/composables/useTemplateCollection.ts +229 -0
  375. package/src/composables/useTextSearch.ts +60 -0
  376. package/src/composables/useTheme.ts +2 -28
  377. package/src/composables/useTimeUtils.ts +26 -2
  378. package/src/composables/useWellPlateEditor.ts +13 -9
  379. package/src/index.ts +11 -348
  380. package/src/install.ts +11 -4
  381. package/src/stores/settings.ts +13 -9
  382. package/src/styles/components/app-layout.css +82 -0
  383. package/src/styles/components/app-page-selector.css +23 -0
  384. package/src/styles/components/app-pill-nav.css +77 -0
  385. package/src/styles/components/app-sidebar.css +119 -0
  386. package/src/styles/components/app-top-bar.css +0 -201
  387. package/src/styles/components/concentration-input.css +3 -142
  388. package/src/styles/components/empty-state.css +0 -16
  389. package/src/styles/components/theme-toggle.css +3 -66
  390. package/src/styles/index.css +0 -2
  391. package/src/templates/adapters.ts +785 -0
  392. package/src/templates/builders.ts +2149 -0
  393. package/src/templates/catalog.ts +245 -0
  394. package/src/templates/componentBindings.ts +653 -0
  395. package/src/templates/controlSchemas.ts +718 -0
  396. package/src/templates/index.ts +318 -0
  397. package/src/templates/lookup.ts +18 -0
  398. package/src/templates/packs.ts +156 -0
  399. package/src/templates/presets.ts +146 -0
  400. package/src/templates/types.ts +668 -0
  401. package/src/types/components.ts +39 -27
  402. package/src/types/form-builder.ts +7 -2
  403. package/src/types/index.ts +13 -3
  404. package/src/types/platform.ts +7 -1
  405. package/src/utils/formModelSync.ts +52 -0
  406. package/src/utils/items.ts +28 -0
  407. package/src/utils/options.ts +23 -0
  408. package/src/utils/pluginIcon.ts +30 -0
  409. package/dist/__tests__/composables/usePluginApi.test.d.ts +0 -13
  410. package/dist/auth-DsI0rQ7_.js.map +0 -1
  411. package/dist/components/GroupingModal.vue.d.ts +0 -12
  412. package/dist/components-_XqPEhP9.js.map +0 -1
  413. package/dist/composables/usePluginApi.d.ts +0 -29
  414. package/dist/composables-tiZqLu1M.js.map +0 -1
  415. package/dist/useScheduleDrag-CA9sGNJG.js +0 -7181
  416. package/dist/useScheduleDrag-CA9sGNJG.js.map +0 -1
  417. package/src/__tests__/composables/usePluginApi.test.ts +0 -81
  418. package/src/components/AppPageSelector.vue +0 -159
  419. package/src/components/AppPillNav.vue +0 -66
  420. package/src/components/GroupingModal.story.vue +0 -52
  421. package/src/components/GroupingModal.vue +0 -422
  422. package/src/components/SettingsButton.story.vue +0 -58
  423. package/src/components/SettingsButton.vue +0 -76
  424. package/src/composables/usePluginApi.ts +0 -39
  425. package/src/styles/components/grouping-modal.css +0 -323
  426. package/src/styles/components/settings-button.css +0 -94
  427. /package/dist/components/{ToastNotification.vue.d.ts → AppToastContainer.vue.d.ts} +0 -0
@@ -0,0 +1,466 @@
1
+ import { computed, shallowRef, watch, type ComputedRef, type Ref } from 'vue'
2
+ import { useApi } from './useApi'
3
+ import { usePlatformContext } from './usePlatformContext'
4
+ import { useRequestSyncState } from './useRequestSyncState'
5
+ import {
6
+ currentExperimentFromContext,
7
+ getInjectedPlatformContext,
8
+ resolveCurrentExperimentId,
9
+ } from './platformContextHelpers'
10
+ import type { ExperimentSummary, PageSelectorItem } from '../types'
11
+
12
+ export type PluginHttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'
13
+
14
+ export interface PluginEndpointContract {
15
+ name: string
16
+ method: PluginHttpMethod
17
+ path: string
18
+ pathParams?: string[]
19
+ queryParams?: PluginEndpointParamContract[]
20
+ requestType?: string | null
21
+ responseType?: string | null
22
+ }
23
+
24
+ export interface PluginEndpointParamContract {
25
+ name: string
26
+ fieldName: string
27
+ type?: string
28
+ required?: boolean
29
+ }
30
+
31
+ export interface PluginNavItemContract {
32
+ path: string
33
+ label: string
34
+ id?: string
35
+ icon?: string
36
+ description?: string
37
+ requiresAuth?: boolean
38
+ requiresAdmin?: boolean
39
+ requiresFeature?: string
40
+ }
41
+
42
+ export interface PluginContract {
43
+ schemaVersion: number
44
+ plugin: {
45
+ name?: string
46
+ packageName?: string
47
+ version?: string
48
+ description?: string
49
+ routesPrefix: string
50
+ apiPrefix: string
51
+ type: 'analysis' | 'experiment-design' | string
52
+ analysisType?: string
53
+ icon?: string
54
+ color?: string
55
+ navItems?: PluginNavItemContract[]
56
+ capabilities?: Record<string, unknown>
57
+ }
58
+ endpoints: PluginEndpointContract[]
59
+ hash: string
60
+ }
61
+
62
+ export interface PluginEndpointDefinition {
63
+ method: PluginHttpMethod
64
+ path: string
65
+ pathParams?: string[]
66
+ queryParams?: PluginEndpointParamDefinition[]
67
+ hasBody?: boolean
68
+ }
69
+
70
+ export type PluginEndpointParamDefinition = string | {
71
+ name: string
72
+ fieldName?: string
73
+ type?: string
74
+ required?: boolean
75
+ }
76
+
77
+ export interface CreatePluginClientOptions {
78
+ endpoints: Record<string, PluginEndpointDefinition>
79
+ baseUrl?: string
80
+ }
81
+
82
+ export interface BuildPluginEndpointUrlOptions {
83
+ /** Override the generated or platform-injected API base URL. */
84
+ baseUrl?: string
85
+ /** Return only the endpoint path and query string instead of base URL + path. */
86
+ includeBaseUrl?: boolean
87
+ }
88
+
89
+ export interface UseCurrentExperimentOptions {
90
+ apiBaseUrl?: string
91
+ immediate?: boolean
92
+ }
93
+
94
+ export interface UseCurrentExperimentReturn<TExperiment = ExperimentSummary> {
95
+ /** Current experiment id resolved from platform injection or URL conventions. */
96
+ experimentId: ComputedRef<number | undefined>
97
+ /** Whether a current experiment id is available. */
98
+ hasExperiment: ComputedRef<boolean>
99
+ /** Current experiment payload, if injected or successfully fetched. */
100
+ experiment: Ref<TExperiment | undefined>
101
+ /** Whether the current experiment is currently loading. */
102
+ isLoading: Ref<boolean>
103
+ /** Error message from the last failed current-experiment fetch, or null. */
104
+ error: Ref<string | null>
105
+ /** Timestamp of the last successful current-experiment fetch, or null. */
106
+ lastLoadedAt: Ref<Date | null>
107
+ /** Return the current experiment id or throw a clear SDK error when absent. */
108
+ requireExperimentId: () => number
109
+ /** Fetch a specific experiment id, defaulting to the current experiment id. */
110
+ fetch: (experimentId?: number) => Promise<TExperiment | undefined>
111
+ /** Refetch the currently resolved experiment id. */
112
+ refresh: () => Promise<TExperiment | undefined>
113
+ }
114
+
115
+ export type PluginEndpointCaller = (payload?: unknown) => Promise<unknown>
116
+ export type GeneratedPluginClient = Record<string, (...args: any[]) => Promise<any>>
117
+
118
+ function normalizeApiBaseUrl(baseUrl: string | undefined): string | undefined {
119
+ if (!baseUrl) return undefined
120
+ return baseUrl.length > 1 ? baseUrl.replace(/\/+$/, '') : baseUrl
121
+ }
122
+
123
+ function normalizeApiPrefix(prefix: string | undefined): string | undefined {
124
+ if (!prefix) return undefined
125
+ if (prefix.startsWith('/api/')) return normalizeApiBaseUrl(prefix)
126
+ if (prefix === '/api') return prefix
127
+ if (prefix.startsWith('/')) return normalizeApiBaseUrl(`/api${prefix}`)
128
+ return normalizeApiBaseUrl(`/api/${prefix}`)
129
+ }
130
+
131
+ function normalizePluginNavPath(path: string): string {
132
+ const raw = path.trim() || '/'
133
+ const prefixed = raw.startsWith('/') ? raw : `/${raw}`
134
+ return prefixed.replace(/\/+$/, '') || '/'
135
+ }
136
+
137
+ function pluginPageIdFromPath(path: string, fallbackIndex: number): string {
138
+ const normalizedPath = normalizePluginNavPath(path)
139
+ if (normalizedPath === '/') return 'dashboard'
140
+ return normalizedPath.replace(/^\/+/, '').replace(/\/+/g, '-') || `page-${fallbackIndex + 1}`
141
+ }
142
+
143
+ /** Convert PluginMetadata.nav_items into AppTopBar pageSelector items for topbar page switches, homepage PluginCards, and fallback views. */
144
+ export function getPluginPageSelectorItems(contract: PluginContract): PageSelectorItem[] {
145
+ const pluginIcon = contract.plugin.icon ?? ''
146
+ const navItems = contract.plugin.navItems ?? []
147
+ const source: PluginNavItemContract[] = navItems.length > 0
148
+ ? navItems
149
+ : [{
150
+ path: '/',
151
+ label: contract.plugin.name ?? 'Dashboard',
152
+ id: 'dashboard',
153
+ icon: pluginIcon || undefined,
154
+ description: contract.plugin.description,
155
+ }]
156
+
157
+ return source.map((item, index) => ({
158
+ id: item.id || pluginPageIdFromPath(item.path, index),
159
+ label: item.label,
160
+ to: normalizePluginNavPath(item.path),
161
+ icon: item.icon || pluginIcon || undefined,
162
+ hint: item.description || contract.plugin.name,
163
+ }))
164
+ }
165
+
166
+ /** Resolve the runtime plugin API base URL from env, explicit options, platform injection, or contract metadata. */
167
+ export function resolvePluginBaseUrl(contract: PluginContract, explicitBaseUrl?: string): string {
168
+ const envPrefix = (import.meta.env?.VITE_API_PREFIX as string | undefined) || undefined
169
+ if (envPrefix) return normalizeApiBaseUrl(envPrefix) ?? envPrefix
170
+ if (explicitBaseUrl) return normalizeApiBaseUrl(explicitBaseUrl) ?? explicitBaseUrl
171
+
172
+ const injected = getInjectedPlatformContext()
173
+ const platformPrefix =
174
+ normalizeApiBaseUrl(injected?.plugin?.api_prefix) ||
175
+ normalizeApiPrefix(injected?.plugin?.route_prefix)
176
+ if (platformPrefix) return platformPrefix
177
+
178
+ return normalizeApiBaseUrl(contract.plugin.apiPrefix) || '/api'
179
+ }
180
+
181
+ function encodePath(path: string, payload: Record<string, unknown>, pathParams: string[]): string {
182
+ let nextPath = path
183
+ for (const param of pathParams) {
184
+ const value = payload[param]
185
+ if (value === undefined || value === null) {
186
+ throw new Error(`[MINT SDK] Missing path parameter '${param}' for plugin endpoint ${path}`)
187
+ }
188
+ nextPath = nextPath.replace(`{${param}}`, encodeURIComponent(String(value)))
189
+ nextPath = nextPath.replace(`:${param}`, encodeURIComponent(String(value)))
190
+ }
191
+ return nextPath
192
+ }
193
+
194
+ function withDefaultPathParams(
195
+ payload: Record<string, unknown>,
196
+ pathParams: string[],
197
+ ): Record<string, unknown> {
198
+ if (!pathParams.length) return payload
199
+ const nextPayload = { ...payload }
200
+ for (const param of pathParams) {
201
+ if (nextPayload[param] !== undefined && nextPayload[param] !== null) continue
202
+ if (isExperimentPathParam(param)) {
203
+ const experimentId = resolveCurrentExperimentId()
204
+ if (experimentId !== undefined) {
205
+ nextPayload[param] = experimentId
206
+ }
207
+ }
208
+ }
209
+ return nextPayload
210
+ }
211
+
212
+ function isExperimentPathParam(param: string): boolean {
213
+ return param === 'experimentId' || param === 'experiment_id'
214
+ }
215
+
216
+ function withoutPathParams(payload: Record<string, unknown>, pathParams: string[]) {
217
+ const copy = { ...payload }
218
+ for (const param of pathParams) {
219
+ delete copy[param]
220
+ }
221
+ return copy
222
+ }
223
+
224
+ function queryParamsFromPayload(
225
+ payload: Record<string, unknown>,
226
+ pathParams: string[],
227
+ queryParams?: PluginEndpointParamDefinition[],
228
+ endpointPath?: string,
229
+ includeUnspecifiedParams = false,
230
+ ) {
231
+ if (!queryParams?.length) {
232
+ return includeUnspecifiedParams ? withoutPathParams(payload, pathParams) : {}
233
+ }
234
+
235
+ const query: Record<string, unknown> = {}
236
+ for (const param of queryParams) {
237
+ const name = typeof param === 'string' ? param : param.name
238
+ const fieldName = typeof param === 'string' ? param : param.fieldName ?? param.name
239
+ const required = typeof param !== 'string' && param.required === true
240
+ const value = payload[fieldName]
241
+ if (value === undefined || value === null) {
242
+ if (required) {
243
+ throw new Error(
244
+ `[MINT SDK] Missing query parameter '${fieldName}' for plugin endpoint ${endpointPath ?? ''}`,
245
+ )
246
+ }
247
+ continue
248
+ }
249
+ query[name] = value
250
+ }
251
+ return query
252
+ }
253
+
254
+ function hasKeys(value: Record<string, unknown>): boolean {
255
+ return Object.keys(value).length > 0
256
+ }
257
+
258
+ function requestBodyFromPayload(
259
+ payload: unknown,
260
+ requestPayload: Record<string, unknown>,
261
+ endpoint: PluginEndpointDefinition,
262
+ ): unknown {
263
+ if (!endpoint.hasBody) return undefined
264
+ const body = endpoint.pathParams?.length || endpoint.queryParams?.length
265
+ ? requestPayload.body
266
+ : payload
267
+ if (body === undefined || body === null) {
268
+ throw new Error(`[MINT SDK] Missing request body for plugin endpoint ${endpoint.path}`)
269
+ }
270
+ return body
271
+ }
272
+
273
+ function pluginEndpointRequestParts(
274
+ contract: PluginContract,
275
+ endpoint: PluginEndpointDefinition,
276
+ payload?: unknown,
277
+ explicitBaseUrl?: string,
278
+ ) {
279
+ const payloadObject =
280
+ payload && typeof payload === 'object' && !Array.isArray(payload)
281
+ ? payload as Record<string, unknown>
282
+ : {}
283
+ const pathParams = endpoint.pathParams ?? []
284
+ const requestPayload = withDefaultPathParams(payloadObject, pathParams)
285
+ const path = encodePath(endpoint.path, requestPayload, pathParams)
286
+ const query = queryParamsFromPayload(
287
+ requestPayload,
288
+ pathParams,
289
+ endpoint.queryParams,
290
+ endpoint.path,
291
+ endpoint.method === 'get',
292
+ )
293
+
294
+ return {
295
+ baseUrl: resolvePluginBaseUrl(contract, explicitBaseUrl),
296
+ path,
297
+ query,
298
+ requestPayload,
299
+ }
300
+ }
301
+
302
+ function appendQueryValue(params: URLSearchParams, key: string, value: unknown): void {
303
+ if (value === undefined || value === null) return
304
+ if (Array.isArray(value)) {
305
+ for (const item of value) {
306
+ appendQueryValue(params, key, item)
307
+ }
308
+ return
309
+ }
310
+ if (typeof value === 'object') {
311
+ params.append(key, JSON.stringify(value))
312
+ return
313
+ }
314
+ params.append(key, String(value))
315
+ }
316
+
317
+ function queryString(query: Record<string, unknown>): string {
318
+ const params = new URLSearchParams()
319
+ for (const [key, value] of Object.entries(query)) {
320
+ appendQueryValue(params, key, value)
321
+ }
322
+ const serialized = params.toString()
323
+ return serialized ? `?${serialized}` : ''
324
+ }
325
+
326
+ function joinBaseUrl(baseUrl: string, path: string): string {
327
+ if (!baseUrl) return path
328
+ const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl
329
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`
330
+ return `${normalizedBase}${normalizedPath}`
331
+ }
332
+
333
+ /** Build the concrete URL for a generated plugin endpoint without making a request. */
334
+ export function buildPluginEndpointUrl(
335
+ contract: PluginContract,
336
+ endpoint: PluginEndpointDefinition,
337
+ payload?: unknown,
338
+ options: BuildPluginEndpointUrlOptions = {},
339
+ ): string {
340
+ const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl)
341
+ const pathWithQuery = `${parts.path}${queryString(parts.query)}`
342
+ if (options.includeBaseUrl === false) return pathWithQuery
343
+ return joinBaseUrl(parts.baseUrl, pathWithQuery)
344
+ }
345
+
346
+ /** Create a typed plugin API client from a generated MINT plugin contract. */
347
+ export function createPluginClient<TClient extends object = GeneratedPluginClient>(
348
+ contract: PluginContract,
349
+ options: CreatePluginClientOptions,
350
+ ): TClient {
351
+ const client: Record<string, PluginEndpointCaller> = {}
352
+
353
+ for (const [name, endpoint] of Object.entries(options.endpoints)) {
354
+ client[name] = async (payload?: unknown) => {
355
+ const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl)
356
+ const api = useApi({ baseUrl: parts.baseUrl })
357
+ const queryConfig = hasKeys(parts.query) ? { params: parts.query } : undefined
358
+
359
+ if (endpoint.method === 'get') {
360
+ return api.get(parts.path, queryConfig)
361
+ }
362
+
363
+ if (endpoint.method === 'delete') {
364
+ const body = requestBodyFromPayload(payload, parts.requestPayload, endpoint)
365
+ const config = body === undefined
366
+ ? queryConfig
367
+ : { ...queryConfig, data: body }
368
+ return api.delete(parts.path, config)
369
+ }
370
+
371
+ const body = requestBodyFromPayload(payload, parts.requestPayload, endpoint)
372
+
373
+ if (endpoint.method === 'post') return api.post(parts.path, body, queryConfig)
374
+ if (endpoint.method === 'put') return api.put(parts.path, body, queryConfig)
375
+ if (endpoint.method === 'patch') return api.patch(parts.path, body, queryConfig)
376
+ throw new Error(`[MINT SDK] Unsupported plugin endpoint method: ${endpoint.method}`)
377
+ }
378
+ }
379
+
380
+ return client as TClient
381
+ }
382
+
383
+ /** Return a generated plugin client from setup code with a stable typed identity. */
384
+ export function usePluginClient<TClient>(client: TClient): TClient {
385
+ return client
386
+ }
387
+
388
+ /** Read plugin settings exposed through the platform context. */
389
+ export function usePluginSettings<TSettings = Record<string, unknown>>() {
390
+ const { plugin } = usePlatformContext()
391
+ const settings = computed(() => plugin.value?.settings as TSettings | undefined)
392
+ return { settings }
393
+ }
394
+
395
+ /** Read and optionally load the current platform experiment for integrated plugin views. */
396
+ export function useCurrentExperiment<TExperiment = ExperimentSummary>(
397
+ options: UseCurrentExperimentOptions = {},
398
+ ): UseCurrentExperimentReturn<TExperiment> {
399
+ const api = useApi({ baseUrl: options.apiBaseUrl ?? getInjectedPlatformContext()?.platformApiUrl })
400
+ const experiment = shallowRef<TExperiment | undefined>(currentExperimentFromContext<TExperiment>())
401
+ const request = useRequestSyncState('Failed to load current experiment')
402
+ const isLoading = request.loading
403
+ const error = request.error
404
+ const lastLoadedAt = request.lastLoadedAt
405
+
406
+ const experimentId = computed<number | undefined>(() => {
407
+ return resolveCurrentExperimentId()
408
+ })
409
+ const hasExperiment = computed(() => experimentId.value !== undefined)
410
+
411
+ function requireExperimentId(): number {
412
+ const id = experimentId.value
413
+ if (id === undefined) {
414
+ throw new Error('[MINT SDK] No current experiment is selected.')
415
+ }
416
+ return id
417
+ }
418
+
419
+ async function fetchExperiment(id = experimentId.value): Promise<TExperiment | undefined> {
420
+ if (id === undefined) {
421
+ experiment.value = currentExperimentFromContext<TExperiment>()
422
+ return experiment.value
423
+ }
424
+
425
+ try {
426
+ const result = await request.run(
427
+ () => api.get<TExperiment>(`/experiments/${id}`),
428
+ { success: 'load', errorMessage: 'Failed to load current experiment' },
429
+ )
430
+ experiment.value = result
431
+ return result
432
+ } catch (e) {
433
+ experiment.value = undefined
434
+ return undefined
435
+ }
436
+ }
437
+
438
+ async function refresh(): Promise<TExperiment | undefined> {
439
+ return fetchExperiment()
440
+ }
441
+
442
+ watch(
443
+ experimentId,
444
+ (id) => {
445
+ if (options.immediate === false) return
446
+ if (id === undefined) {
447
+ experiment.value = currentExperimentFromContext<TExperiment>()
448
+ return
449
+ }
450
+ void fetchExperiment(id)
451
+ },
452
+ { immediate: options.immediate !== false },
453
+ )
454
+
455
+ return {
456
+ experimentId,
457
+ hasExperiment,
458
+ experiment,
459
+ isLoading,
460
+ error,
461
+ lastLoadedAt,
462
+ requireExperimentId,
463
+ fetch: fetchExperiment,
464
+ refresh,
465
+ }
466
+ }
@@ -1,15 +1,28 @@
1
1
  import { ref, computed, onMounted, type Ref, type ComputedRef } from 'vue'
2
2
  import { useApi } from './useApi'
3
3
  import { usePlatformContext } from './usePlatformContext'
4
+ import { useRequestSyncState } from './useRequestSyncState'
4
5
 
5
6
  export interface UsePluginConfigReturn {
7
+ /** Editable plugin configuration values. */
6
8
  config: Ref<Record<string, unknown>>
9
+ /** Whether the config is currently loading. */
7
10
  isLoading: Ref<boolean>
11
+ /** Whether the config is currently saving. */
8
12
  isSaving: Ref<boolean>
13
+ /** Error message from the last failed operation, or null. */
9
14
  error: Ref<string | null>
15
+ /** Timestamp of the last successful load, or null. */
16
+ lastLoadedAt: Ref<Date | null>
17
+ /** Timestamp of the last successful save, or null. */
18
+ lastSavedAt: Ref<Date | null>
19
+ /** Whether local config differs from the last loaded or saved snapshot. */
10
20
  isDirty: ComputedRef<boolean>
21
+ /** Load plugin configuration from the platform. */
11
22
  load: () => Promise<void>
23
+ /** Save plugin configuration to the platform. */
12
24
  save: () => Promise<boolean>
25
+ /** Restore the last loaded or saved configuration snapshot. */
13
26
  reset: () => void
14
27
  }
15
28
 
@@ -22,9 +35,12 @@ export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
22
35
 
23
36
  const config = ref<Record<string, unknown>>({})
24
37
  const savedConfig = ref<Record<string, unknown>>({})
38
+ const request = useRequestSyncState('Plugin config request failed.')
25
39
  const isLoading = ref(false)
26
40
  const isSaving = ref(false)
27
- const error = ref<string | null>(null)
41
+ const error = request.error
42
+ const lastLoadedAt = request.lastLoadedAt
43
+ const lastSavedAt = request.lastSavedAt
28
44
 
29
45
  const isDirty = computed(() => {
30
46
  return JSON.stringify(config.value) !== JSON.stringify(savedConfig.value)
@@ -35,15 +51,17 @@ export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
35
51
  if (!name) return
36
52
 
37
53
  isLoading.value = true
38
- error.value = null
39
54
  try {
40
- const response = await api.get<{ plugin_name: string; config: Record<string, unknown> }>(
41
- `/plugins/${encodeURIComponent(name)}/config`,
55
+ const response = await request.run(
56
+ () => api.get<{ plugin_name: string; config: Record<string, unknown> }>(
57
+ `/plugins/${encodeURIComponent(name)}/config`,
58
+ ),
59
+ { success: 'load', errorMessage: 'Failed to load plugin config' },
42
60
  )
43
61
  config.value = { ...response.config }
44
62
  savedConfig.value = { ...response.config }
45
- } catch (e) {
46
- error.value = e instanceof Error ? e.message : 'Failed to load plugin config'
63
+ } catch {
64
+ // Error state is handled by request.run().
47
65
  } finally {
48
66
  isLoading.value = false
49
67
  }
@@ -54,17 +72,18 @@ export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
54
72
  if (!name) return false
55
73
 
56
74
  isSaving.value = true
57
- error.value = null
58
75
  try {
59
- const response = await api.patch<{ plugin_name: string; config: Record<string, unknown> }>(
60
- `/plugins/${encodeURIComponent(name)}/config`,
61
- { config: config.value },
76
+ const response = await request.run(
77
+ () => api.patch<{ plugin_name: string; config: Record<string, unknown> }>(
78
+ `/plugins/${encodeURIComponent(name)}/config`,
79
+ { config: config.value },
80
+ ),
81
+ { success: 'save', errorMessage: 'Failed to save plugin config' },
62
82
  )
63
83
  config.value = { ...response.config }
64
84
  savedConfig.value = { ...response.config }
65
85
  return true
66
- } catch (e) {
67
- error.value = e instanceof Error ? e.message : 'Failed to save plugin config'
86
+ } catch {
68
87
  return false
69
88
  } finally {
70
89
  isSaving.value = false
@@ -73,7 +92,7 @@ export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
73
92
 
74
93
  function reset(): void {
75
94
  config.value = { ...savedConfig.value }
76
- error.value = null
95
+ request.clearError()
77
96
  }
78
97
 
79
98
  onMounted(() => {
@@ -85,6 +104,8 @@ export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
85
104
  isLoading,
86
105
  isSaving,
87
106
  error,
107
+ lastLoadedAt,
108
+ lastSavedAt,
88
109
  isDirty,
89
110
  load,
90
111
  save,
@@ -0,0 +1,126 @@
1
+ import { ref, type Ref } from 'vue'
2
+
3
+ export type RequestSyncSuccessKind = 'load' | 'save' | 'run'
4
+
5
+ export interface RequestSyncRunOptions {
6
+ success?: RequestSyncSuccessKind
7
+ errorMessage?: string
8
+ }
9
+
10
+ export interface UseRequestSyncStateReturn {
11
+ /** Whether a request is in progress. */
12
+ loading: Ref<boolean>
13
+ /** Message from the last failed request, or null. */
14
+ error: Ref<string | null>
15
+ /** Timestamp of the last successful load operation, or null. */
16
+ lastLoadedAt: Ref<Date | null>
17
+ /** Timestamp of the last successful save operation, or null. */
18
+ lastSavedAt: Ref<Date | null>
19
+ /** Timestamp of the last successful run operation, or null. */
20
+ lastRunAt: Ref<Date | null>
21
+ /** Clear the current error without changing loading or timestamps. */
22
+ clearError: () => void
23
+ /** Convert thrown values into a developer-facing error message. */
24
+ readErrorMessage: (value: unknown, fallback?: string) => string
25
+ /** Store and return a normalized error message. */
26
+ setError: (value: unknown, fallback?: string) => string
27
+ /** Mark the resource as successfully loaded. */
28
+ markLoaded: (date?: Date) => void
29
+ /** Mark the resource as successfully saved. */
30
+ markSaved: (date?: Date) => void
31
+ /** Mark the operation as successfully run. */
32
+ markRun: (date?: Date) => void
33
+ /** Run a request with shared loading/error handling and optional sync marking. */
34
+ run: <T>(operation: () => Promise<T>, options?: RequestSyncRunOptions) => Promise<T>
35
+ }
36
+
37
+ /** Shared loading/error/timestamp state for generated plugin request helpers. */
38
+ export function useRequestSyncState(
39
+ defaultErrorMessage = 'Request failed.',
40
+ ): UseRequestSyncStateReturn {
41
+ const loading = ref(false)
42
+ const error = ref<string | null>(null)
43
+ const lastLoadedAt = ref<Date | null>(null)
44
+ const lastSavedAt = ref<Date | null>(null)
45
+ const lastRunAt = ref<Date | null>(null)
46
+
47
+ function clearError(): void {
48
+ error.value = null
49
+ }
50
+
51
+ function readErrorMessage(value: unknown, fallback = defaultErrorMessage): string {
52
+ if (value instanceof Error && value.message) {
53
+ return value.message
54
+ }
55
+ if (typeof value === 'string' && value.trim()) {
56
+ return value
57
+ }
58
+ if (
59
+ typeof value === 'object'
60
+ && value !== null
61
+ && 'message' in value
62
+ && typeof (value as { message?: unknown }).message === 'string'
63
+ && (value as { message: string }).message
64
+ ) {
65
+ return (value as { message: string }).message
66
+ }
67
+ return fallback
68
+ }
69
+
70
+ function setError(value: unknown, fallback?: string): string {
71
+ const message = readErrorMessage(value, fallback)
72
+ error.value = message
73
+ return message
74
+ }
75
+
76
+ function markLoaded(date = new Date()): void {
77
+ lastLoadedAt.value = date
78
+ }
79
+
80
+ function markSaved(date = new Date()): void {
81
+ lastSavedAt.value = date
82
+ }
83
+
84
+ function markRun(date = new Date()): void {
85
+ lastRunAt.value = date
86
+ }
87
+
88
+ async function run<T>(
89
+ operation: () => Promise<T>,
90
+ options: RequestSyncRunOptions = {},
91
+ ): Promise<T> {
92
+ loading.value = true
93
+ clearError()
94
+ try {
95
+ const response = await operation()
96
+ if (options.success === 'load') {
97
+ markLoaded()
98
+ } else if (options.success === 'save') {
99
+ markSaved()
100
+ } else if (options.success === 'run') {
101
+ markRun()
102
+ }
103
+ return response
104
+ } catch (err) {
105
+ setError(err, options.errorMessage)
106
+ throw err
107
+ } finally {
108
+ loading.value = false
109
+ }
110
+ }
111
+
112
+ return {
113
+ loading,
114
+ error,
115
+ lastLoadedAt,
116
+ lastSavedAt,
117
+ lastRunAt,
118
+ clearError,
119
+ readErrorMessage,
120
+ setError,
121
+ markLoaded,
122
+ markSaved,
123
+ markRun,
124
+ run,
125
+ }
126
+ }