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

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 (399) hide show
  1. package/README.md +218 -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/ConcentrationInput.test.d.ts +1 -0
  19. package/dist/__tests__/components/ControlWorkspaceView.test.d.ts +1 -0
  20. package/dist/__tests__/components/DatePicker.test.d.ts +1 -0
  21. package/dist/__tests__/components/DateTimePicker.test.d.ts +1 -0
  22. package/dist/__tests__/components/EmptyState.test.d.ts +1 -0
  23. package/dist/__tests__/components/ExperimentPopover.test.d.ts +1 -0
  24. package/dist/__tests__/components/FormBuilder.test.d.ts +1 -0
  25. package/dist/__tests__/components/FormCompatibility.test.d.ts +1 -0
  26. package/dist/__tests__/components/GroupAssigner.test.d.ts +1 -0
  27. package/dist/__tests__/components/GroupingModal.test.d.ts +1 -0
  28. package/dist/__tests__/components/MultiSelect.test.d.ts +1 -0
  29. package/dist/__tests__/components/ProtocolStepEditor.test.d.ts +1 -0
  30. package/dist/__tests__/components/ReagentList.test.d.ts +1 -0
  31. package/dist/__tests__/components/SampleHierarchyTree.test.d.ts +1 -0
  32. package/dist/__tests__/components/SampleSelector.test.d.ts +1 -0
  33. package/dist/__tests__/components/SegmentedControl.test.d.ts +1 -0
  34. package/dist/__tests__/components/SettingsButton.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/useBioTemplatePackWorkspace.test.d.ts +1 -0
  40. package/dist/__tests__/composables/useBioTemplatePresetWorkspace.test.d.ts +1 -0
  41. package/dist/__tests__/composables/useBioTemplateWorkspace.test.d.ts +1 -0
  42. package/dist/__tests__/composables/useCalendarGrid.test.d.ts +1 -0
  43. package/dist/__tests__/composables/useControlSchema.test.d.ts +1 -0
  44. package/dist/__tests__/composables/useDebouncedWatch.test.d.ts +1 -0
  45. package/dist/__tests__/composables/useDropdownState.test.d.ts +1 -0
  46. package/dist/__tests__/composables/useEventListener.test.d.ts +1 -0
  47. package/dist/__tests__/composables/useExpansionSet.test.d.ts +1 -0
  48. package/dist/__tests__/composables/useExperimentData.test.d.ts +1 -0
  49. package/dist/__tests__/composables/useExperimentSelector.test.d.ts +1 -0
  50. package/dist/__tests__/composables/useGroupAssignment.test.d.ts +1 -0
  51. package/dist/__tests__/composables/useListSelection.test.d.ts +1 -0
  52. package/dist/__tests__/composables/usePluginClient.test.d.ts +1 -0
  53. package/dist/__tests__/composables/usePluginConfig.test.d.ts +1 -0
  54. package/dist/__tests__/composables/useRequestSyncState.test.d.ts +1 -0
  55. package/dist/__tests__/composables/useSampleGroups.test.d.ts +1 -0
  56. package/dist/__tests__/composables/useSelectionLimit.test.d.ts +1 -0
  57. package/dist/__tests__/composables/useSortedItems.test.d.ts +1 -0
  58. package/dist/__tests__/composables/useTemplateCollection.test.d.ts +1 -0
  59. package/dist/__tests__/composables/useTextSearch.test.d.ts +1 -0
  60. package/dist/__tests__/composables/useTheme.test.d.ts +1 -0
  61. package/dist/__tests__/composables/useTimeUtils.test.d.ts +1 -0
  62. package/dist/__tests__/docs/frontendDocsCatalog.test.d.ts +1 -0
  63. package/dist/__tests__/templates/templates.test.d.ts +1 -0
  64. package/dist/{auth-DsI0rQ7_.js → auth-QQj2kkze.js} +12 -5
  65. package/dist/auth-QQj2kkze.js.map +1 -0
  66. package/dist/components/ActionItem.vue.d.ts +32 -0
  67. package/dist/components/AppAvatarMenu.vue.d.ts +2 -7
  68. package/dist/components/AppPageSelector.vue.d.ts +3 -6
  69. package/dist/components/AppPillNav.vue.d.ts +2 -2
  70. package/dist/components/AppSidebar.vue.d.ts +56 -3
  71. package/dist/components/AppToastContainer.vue.d.ts +2 -0
  72. package/dist/components/AppTopBar.vue.d.ts +41 -10
  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 +117 -0
  83. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +92 -0
  84. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +82 -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/CalendarGridPanel.vue.d.ts +25 -0
  89. package/dist/components/CollapsibleCard.vue.d.ts +1 -1
  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 +130 -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/DropdownButton.vue.d.ts +3 -3
  97. package/dist/components/EmptyState.vue.d.ts +1 -2
  98. package/dist/components/ExperimentDataViewer.vue.d.ts +1 -1
  99. package/dist/components/ExperimentTimeline.vue.d.ts +2 -2
  100. package/dist/components/FileUploader.vue.d.ts +1 -1
  101. package/dist/components/FitPanel.vue.d.ts +1 -1
  102. package/dist/components/FormActions.vue.d.ts +4 -4
  103. package/dist/components/FormBuilder.vue.d.ts +22 -8
  104. package/dist/components/FormFieldRenderer.vue.d.ts +7 -10
  105. package/dist/components/FormSection.vue.d.ts +11 -24
  106. package/dist/components/FormulaInput.vue.d.ts +2 -2
  107. package/dist/components/MoleculeInput.vue.d.ts +2 -2
  108. package/dist/components/MultiSelect.vue.d.ts +3 -3
  109. package/dist/components/NumberInput.vue.d.ts +1 -1
  110. package/dist/components/ProgressBar.vue.d.ts +1 -1
  111. package/dist/components/ProtocolStepEditor.vue.d.ts +3 -1
  112. package/dist/components/RackEditor.vue.d.ts +2 -2
  113. package/dist/components/SampleLegend.vue.d.ts +2 -2
  114. package/dist/components/ScheduleCalendar.vue.d.ts +2 -2
  115. package/dist/components/SegmentedControl.vue.d.ts +2 -2
  116. package/dist/components/SequenceInput.vue.d.ts +3 -3
  117. package/dist/components/SettingsButton.vue.d.ts +2 -2
  118. package/dist/components/SettingsModal.vue.d.ts +13 -5
  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 +8 -8
  125. package/dist/components/index.d.ts +11 -1
  126. package/dist/components/index.js +3 -3
  127. package/dist/components/internal/FormFieldRendererInternal.vue.d.ts +31 -0
  128. package/dist/components/internal/FormSectionRenderer.vue.d.ts +43 -0
  129. package/dist/{components-_XqPEhP9.js → components-D_Sr0adg.js} +7290 -6518
  130. package/dist/components-D_Sr0adg.js.map +1 -0
  131. package/dist/composables/index.d.ts +21 -2
  132. package/dist/composables/index.js +4 -3
  133. package/dist/composables/platformContextHelpers.d.ts +14 -0
  134. package/dist/composables/useBioTemplateComponents.d.ts +20 -0
  135. package/dist/composables/useBioTemplateControls.d.ts +6 -0
  136. package/dist/composables/useBioTemplatePackWorkspace.d.ts +45 -0
  137. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +74 -0
  138. package/dist/composables/useBioTemplateWorkspace.d.ts +50 -0
  139. package/dist/composables/useCalendarGrid.d.ts +26 -0
  140. package/dist/composables/useControlSchema.d.ts +321 -0
  141. package/dist/composables/useDebouncedWatch.d.ts +20 -0
  142. package/dist/composables/useDropdownState.d.ts +19 -0
  143. package/dist/composables/useEventListener.d.ts +13 -0
  144. package/dist/composables/useExpansionSet.d.ts +21 -0
  145. package/dist/composables/useExperimentData.d.ts +10 -0
  146. package/dist/composables/useExperimentSave.d.ts +31 -2
  147. package/dist/composables/useExperimentSelector.d.ts +20 -0
  148. package/dist/composables/useForm.d.ts +2 -0
  149. package/dist/composables/useGroupAssignment.d.ts +31 -0
  150. package/dist/composables/useListSelection.d.ts +35 -0
  151. package/dist/composables/usePlatformContext.d.ts +21 -3
  152. package/dist/composables/usePluginApi.d.ts +7 -14
  153. package/dist/composables/usePluginClient.d.ts +109 -0
  154. package/dist/composables/usePluginConfig.d.ts +12 -0
  155. package/dist/composables/useRequestSyncState.d.ts +34 -0
  156. package/dist/composables/useSampleGroups.d.ts +32 -0
  157. package/dist/composables/useSelectionLimit.d.ts +17 -0
  158. package/dist/composables/useSortedItems.d.ts +32 -0
  159. package/dist/composables/useTemplateCollection.d.ts +58 -0
  160. package/dist/composables/useTextSearch.d.ts +18 -0
  161. package/dist/composables/useTimeUtils.d.ts +8 -0
  162. package/dist/{composables-tiZqLu1M.js → composables-C3dpXQN5.js} +228 -146
  163. package/dist/composables-C3dpXQN5.js.map +1 -0
  164. package/dist/index.d.ts +12 -3
  165. package/dist/index.js +6 -5
  166. package/dist/install.d.ts +7 -2
  167. package/dist/install.js +2 -2
  168. package/dist/install.js.map +1 -1
  169. package/dist/stores/index.js +1 -1
  170. package/dist/stores/settings.d.ts +4 -1
  171. package/dist/styles.css +5235 -5977
  172. package/dist/templates/adapters.d.ts +43 -0
  173. package/dist/templates/builders.d.ts +63 -0
  174. package/dist/templates/catalog.d.ts +188 -0
  175. package/dist/templates/componentBindings.d.ts +58 -0
  176. package/dist/templates/controlSchemas.d.ts +25 -0
  177. package/dist/templates/index.d.ts +15 -0
  178. package/dist/templates/index.js +2 -0
  179. package/dist/templates/lookup.d.ts +4 -0
  180. package/dist/templates/packs.d.ts +18 -0
  181. package/dist/templates/presets.d.ts +90 -0
  182. package/dist/templates/types.d.ts +531 -0
  183. package/dist/templates-50NPjaxL.js +9333 -0
  184. package/dist/templates-50NPjaxL.js.map +1 -0
  185. package/dist/types/components.d.ts +26 -4
  186. package/dist/types/form-builder.d.ts +6 -8
  187. package/dist/types/index.d.ts +2 -2
  188. package/dist/types/platform.d.ts +7 -1
  189. package/dist/useScheduleDrag-D4oWdh41.js +4371 -0
  190. package/dist/useScheduleDrag-D4oWdh41.js.map +1 -0
  191. package/dist/utils/formModelSync.d.ts +5 -0
  192. package/dist/utils/items.d.ts +8 -0
  193. package/dist/utils/options.d.ts +6 -0
  194. package/dist/utils/pluginIcon.d.ts +9 -0
  195. package/package.json +7 -2
  196. package/src/__tests__/components/ActionItem.test.ts +99 -0
  197. package/src/__tests__/components/AppAvatarMenu.test.ts +27 -0
  198. package/src/__tests__/components/AppPageSelector.test.ts +134 -0
  199. package/src/__tests__/components/AppPillNav.test.ts +78 -0
  200. package/src/__tests__/components/AppPluginSwitcher.test.ts +44 -0
  201. package/src/__tests__/components/AppSidebar.test.ts +370 -0
  202. package/src/__tests__/components/AppToastContainer.test.ts +48 -0
  203. package/src/__tests__/components/AppTopBar.test.ts +383 -0
  204. package/src/__tests__/components/BaseRadioGroup.test.ts +25 -0
  205. package/src/__tests__/components/BaseSelect.test.ts +21 -0
  206. package/src/__tests__/components/BaseTabs.test.ts +25 -0
  207. package/src/__tests__/components/BatchProgressList.test.ts +52 -0
  208. package/src/__tests__/components/BioTemplateExperimentWorkspaceView.test.ts +153 -0
  209. package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +161 -0
  210. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +281 -0
  211. package/src/__tests__/components/BioTemplateRenderer.test.ts +71 -0
  212. package/src/__tests__/components/Breadcrumb.test.ts +23 -0
  213. package/src/__tests__/components/CalendarGridPanel.test.ts +36 -0
  214. package/src/__tests__/components/ConcentrationInput.test.ts +45 -0
  215. package/src/__tests__/components/ControlWorkspaceView.test.ts +1031 -0
  216. package/src/__tests__/components/DataFrame.test.ts +11 -0
  217. package/src/__tests__/components/DatePicker.test.ts +45 -0
  218. package/src/__tests__/components/DateTimePicker.test.ts +48 -0
  219. package/src/__tests__/components/DropdownButton.test.ts +23 -0
  220. package/src/__tests__/components/EmptyState.test.ts +23 -0
  221. package/src/__tests__/components/ExperimentPopover.test.ts +56 -0
  222. package/src/__tests__/components/FormBuilder.test.ts +296 -0
  223. package/src/__tests__/components/FormCompatibility.test.ts +94 -0
  224. package/src/__tests__/components/GroupAssigner.test.ts +30 -0
  225. package/src/__tests__/components/GroupingModal.test.ts +73 -0
  226. package/src/__tests__/components/MultiSelect.test.ts +48 -0
  227. package/src/__tests__/components/ProtocolStepEditor.test.ts +33 -0
  228. package/src/__tests__/components/ReagentList.test.ts +82 -0
  229. package/src/__tests__/components/SampleHierarchyTree.test.ts +53 -0
  230. package/src/__tests__/components/SampleSelector.test.ts +60 -0
  231. package/src/__tests__/components/SegmentedControl.test.ts +24 -0
  232. package/src/__tests__/components/SettingsButton.test.ts +44 -0
  233. package/src/__tests__/components/SettingsModal.test.ts +296 -0
  234. package/src/__tests__/components/TagsInput.test.ts +75 -0
  235. package/src/__tests__/components/ThemeToggle.test.ts +47 -0
  236. package/src/__tests__/components/TimePicker.test.ts +38 -0
  237. package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +122 -0
  238. package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +199 -0
  239. package/src/__tests__/composables/useBioTemplateWorkspace.test.ts +99 -0
  240. package/src/__tests__/composables/useCalendarGrid.test.ts +38 -0
  241. package/src/__tests__/composables/useControlSchema.test.ts +919 -0
  242. package/src/__tests__/composables/useDebouncedWatch.test.ts +93 -0
  243. package/src/__tests__/composables/useDropdownState.test.ts +95 -0
  244. package/src/__tests__/composables/useEventListener.test.ts +116 -0
  245. package/src/__tests__/composables/useExpansionSet.test.ts +62 -0
  246. package/src/__tests__/composables/useExperimentData.test.ts +4 -0
  247. package/src/__tests__/composables/useExperimentSave.test.ts +203 -8
  248. package/src/__tests__/composables/useExperimentSelector.test.ts +164 -0
  249. package/src/__tests__/composables/useForm.test.ts +58 -0
  250. package/src/__tests__/composables/useFormBuilder.test.ts +77 -0
  251. package/src/__tests__/composables/useGroupAssignment.test.ts +73 -0
  252. package/src/__tests__/composables/useListSelection.test.ts +66 -0
  253. package/src/__tests__/composables/usePluginClient.test.ts +444 -0
  254. package/src/__tests__/composables/usePluginConfig.test.ts +5 -0
  255. package/src/__tests__/composables/useRequestSyncState.test.ts +92 -0
  256. package/src/__tests__/composables/useSampleGroups.test.ts +66 -0
  257. package/src/__tests__/composables/useSelectionLimit.test.ts +41 -0
  258. package/src/__tests__/composables/useSortedItems.test.ts +87 -0
  259. package/src/__tests__/composables/useTemplateCollection.test.ts +147 -0
  260. package/src/__tests__/composables/useTextSearch.test.ts +55 -0
  261. package/src/__tests__/composables/useTheme.test.ts +91 -0
  262. package/src/__tests__/composables/useTimeUtils.test.ts +35 -0
  263. package/src/__tests__/docs/frontendDocsCatalog.test.ts +229 -0
  264. package/src/__tests__/fixtures/templates/dose-response.json +81 -0
  265. package/src/__tests__/fixtures/templates/plate-map.json +54 -0
  266. package/src/__tests__/fixtures/templates/qpcr-plate.json +96 -0
  267. package/src/__tests__/fixtures/templates/sample-sheet.json +71 -0
  268. package/src/__tests__/templates/templates.test.ts +1043 -0
  269. package/src/components/ActionItem.vue +82 -0
  270. package/src/components/AppAvatarMenu.vue +15 -69
  271. package/src/components/AppLayout.story.vue +25 -25
  272. package/src/components/AppPageSelector.vue +63 -94
  273. package/src/components/AppPillNav.vue +44 -39
  274. package/src/components/AppPluginSwitcher.vue +41 -145
  275. package/src/components/AppSidebar.story.vue +94 -0
  276. package/src/components/AppSidebar.vue +187 -12
  277. package/src/components/{ToastNotification.story.vue → AppToastContainer.story.vue} +6 -6
  278. package/src/components/AppToastContainer.vue +62 -0
  279. package/src/components/AppTopBar.story.vue +7 -30
  280. package/src/components/AppTopBar.vue +251 -57
  281. package/src/components/BaseModal.vue +3 -5
  282. package/src/components/BaseRadioGroup.vue +7 -3
  283. package/src/components/BaseSelect.vue +11 -7
  284. package/src/components/BaseTabs.vue +6 -4
  285. package/src/components/BatchProgressList.vue +5 -8
  286. package/src/components/BioTemplateExperimentWorkspaceView.story.vue +123 -0
  287. package/src/components/BioTemplateExperimentWorkspaceView.vue +337 -0
  288. package/src/components/BioTemplatePackWorkspaceView.story.vue +107 -0
  289. package/src/components/BioTemplatePackWorkspaceView.vue +176 -0
  290. package/src/components/BioTemplatePresetWorkspaceView.story.vue +151 -0
  291. package/src/components/BioTemplatePresetWorkspaceView.vue +392 -0
  292. package/src/components/BioTemplateRenderer.story.vue +57 -0
  293. package/src/components/BioTemplateRenderer.vue +269 -0
  294. package/src/components/Breadcrumb.vue +14 -8
  295. package/src/components/CalendarGridPanel.vue +120 -0
  296. package/src/components/ConcentrationInput.vue +27 -64
  297. package/src/components/ControlWorkspaceView.story.vue +336 -0
  298. package/src/components/ControlWorkspaceView.vue +347 -0
  299. package/src/components/DataFrame.vue +34 -50
  300. package/src/components/DatePicker.vue +59 -192
  301. package/src/components/DateTimePicker.vue +50 -171
  302. package/src/components/DropdownButton.vue +14 -32
  303. package/src/components/EmptyState.vue +4 -2
  304. package/src/components/ExperimentPopover.vue +5 -22
  305. package/src/components/FormBuilder.vue +124 -27
  306. package/src/components/FormFieldRenderer.vue +15 -38
  307. package/src/components/FormSection.vue +20 -73
  308. package/src/components/GroupAssigner.vue +24 -56
  309. package/src/components/GroupingModal.story.vue +3 -3
  310. package/src/components/GroupingModal.vue +30 -391
  311. package/src/components/MultiSelect.vue +17 -12
  312. package/src/components/PlateMapEditor.vue +3 -8
  313. package/src/components/PluginIcon.vue +2 -22
  314. package/src/components/ProtocolStepEditor.vue +13 -22
  315. package/src/components/ReagentList.vue +25 -33
  316. package/src/components/SampleHierarchyTree.vue +12 -23
  317. package/src/components/SampleSelector.vue +42 -122
  318. package/src/components/SegmentedControl.vue +7 -3
  319. package/src/components/SettingsButton.story.vue +1 -1
  320. package/src/components/SettingsButton.vue +15 -27
  321. package/src/components/SettingsModal.story.vue +1 -1
  322. package/src/components/SettingsModal.vue +120 -29
  323. package/src/components/TagsInput.vue +29 -14
  324. package/src/components/ThemeToggle.vue +9 -7
  325. package/src/components/TimePicker.vue +19 -41
  326. package/src/components/ToastNotification.vue +4 -57
  327. package/src/components/Tooltip.vue +7 -12
  328. package/src/components/WellEditPopup.vue +3 -8
  329. package/src/components/WellPlate.vue +4 -10
  330. package/src/components/index.ts +11 -1
  331. package/src/components/internal/FormFieldRendererInternal.vue +50 -0
  332. package/src/components/internal/FormSectionRenderer.vue +78 -0
  333. package/src/composables/index.ts +212 -0
  334. package/src/composables/platformContextHelpers.ts +74 -0
  335. package/src/composables/useBioTemplateComponents.ts +93 -0
  336. package/src/composables/useBioTemplateControls.ts +41 -0
  337. package/src/composables/useBioTemplatePackWorkspace.ts +181 -0
  338. package/src/composables/useBioTemplatePresetWorkspace.ts +337 -0
  339. package/src/composables/useBioTemplateWorkspace.ts +139 -0
  340. package/src/composables/useCalendarGrid.ts +140 -0
  341. package/src/composables/useControlSchema.ts +1274 -0
  342. package/src/composables/useDebouncedWatch.ts +119 -0
  343. package/src/composables/useDropdownState.ts +83 -0
  344. package/src/composables/useEventListener.ts +111 -0
  345. package/src/composables/useExpansionSet.ts +117 -0
  346. package/src/composables/useExperimentData.ts +20 -11
  347. package/src/composables/useExperimentSave.ts +202 -50
  348. package/src/composables/useExperimentSelector.ts +86 -72
  349. package/src/composables/useForm.ts +49 -4
  350. package/src/composables/useFormBuilder.ts +93 -42
  351. package/src/composables/useGroupAssignment.ts +148 -0
  352. package/src/composables/useListSelection.ts +158 -0
  353. package/src/composables/usePluginApi.ts +7 -14
  354. package/src/composables/usePluginClient.ts +425 -0
  355. package/src/composables/usePluginConfig.ts +34 -13
  356. package/src/composables/useRequestSyncState.ts +126 -0
  357. package/src/composables/useSampleGroups.ts +126 -0
  358. package/src/composables/useSelectionLimit.ts +57 -0
  359. package/src/composables/useSortedItems.ts +118 -0
  360. package/src/composables/useTemplateCollection.ts +229 -0
  361. package/src/composables/useTextSearch.ts +60 -0
  362. package/src/composables/useTheme.ts +2 -28
  363. package/src/composables/useTimeUtils.ts +26 -2
  364. package/src/composables/useWellPlateEditor.ts +13 -9
  365. package/src/index.ts +224 -4
  366. package/src/install.ts +11 -4
  367. package/src/stores/settings.ts +13 -9
  368. package/src/styles/components/app-page-selector.css +23 -0
  369. package/src/styles/components/app-pill-nav.css +7 -0
  370. package/src/styles/components/app-top-bar.css +34 -0
  371. package/src/styles/components/concentration-input.css +3 -142
  372. package/src/styles/components/empty-state.css +0 -16
  373. package/src/styles/components/settings-button.css +3 -66
  374. package/src/styles/components/theme-toggle.css +3 -66
  375. package/src/styles/index.css +0 -1
  376. package/src/templates/adapters.ts +785 -0
  377. package/src/templates/builders.ts +2149 -0
  378. package/src/templates/catalog.ts +245 -0
  379. package/src/templates/componentBindings.ts +615 -0
  380. package/src/templates/controlSchemas.ts +718 -0
  381. package/src/templates/index.ts +314 -0
  382. package/src/templates/lookup.ts +18 -0
  383. package/src/templates/packs.ts +156 -0
  384. package/src/templates/presets.ts +146 -0
  385. package/src/templates/types.ts +668 -0
  386. package/src/types/components.ts +41 -4
  387. package/src/types/form-builder.ts +7 -2
  388. package/src/types/index.ts +14 -0
  389. package/src/types/platform.ts +7 -1
  390. package/src/utils/formModelSync.ts +52 -0
  391. package/src/utils/items.ts +28 -0
  392. package/src/utils/options.ts +23 -0
  393. package/src/utils/pluginIcon.ts +30 -0
  394. package/dist/auth-DsI0rQ7_.js.map +0 -1
  395. package/dist/components-_XqPEhP9.js.map +0 -1
  396. package/dist/composables-tiZqLu1M.js.map +0 -1
  397. package/dist/useScheduleDrag-CA9sGNJG.js +0 -7181
  398. package/dist/useScheduleDrag-CA9sGNJG.js.map +0 -1
  399. package/src/styles/components/grouping-modal.css +0 -323
@@ -1,5 +1,22 @@
1
- import { ref, type Ref } from 'vue'
1
+ import { computed, ref, type ComputedRef, type Ref } from 'vue'
2
2
  import { useApi } from './useApi'
3
+ import { useRequestSyncState } from './useRequestSyncState'
4
+ import { createBioTemplatePresetCollection } from '../templates'
5
+ import type {
6
+ CreateLcmsBatchCollectionOptions,
7
+ CreateQpcrExpressionCollectionOptions,
8
+ CreateWellPlateScreenCollectionOptions,
9
+ TemplatePresetId,
10
+ } from '../templates'
11
+ import {
12
+ getInjectedPlatformContext,
13
+ resolveCurrentExperimentId,
14
+ } from './platformContextHelpers'
15
+
16
+ type TemplatePresetCollectionOptions =
17
+ | CreateWellPlateScreenCollectionOptions
18
+ | CreateQpcrExpressionCollectionOptions
19
+ | CreateLcmsBatchCollectionOptions
3
20
 
4
21
  export interface UseExperimentSaveOptions {
5
22
  /** Default plugin_id for all save/load calls. */
@@ -11,16 +28,39 @@ export interface UseExperimentSaveOptions {
11
28
  }
12
29
 
13
30
  export interface UseExperimentSaveReturn {
31
+ /** Current experiment id from platform injection, URL query, or URL path. */
32
+ currentExperimentId: ComputedRef<number | undefined>
33
+ /** Whether a current experiment id is available. */
34
+ hasCurrentExperiment: ComputedRef<boolean>
14
35
  /** Whether a save operation is in progress. */
15
36
  isSaving: Ref<boolean>
37
+ /** Whether a load or delete operation is in progress. */
38
+ isLoading: Ref<boolean>
16
39
  /** Error message from the last failed operation, or null. */
17
40
  error: Ref<string | null>
41
+ /** Timestamp of the last successful load, or null. */
42
+ lastLoadedAt: Ref<Date | null>
18
43
  /** Timestamp of the last successful save, or null. */
19
44
  lastSavedAt: Ref<Date | null>
20
45
  /** Save design data for an experiment. */
21
46
  saveDesign: (experimentId: number, data: Record<string, unknown>) => Promise<boolean>
47
+ /** Save design data for the current experiment. */
48
+ saveCurrentDesign: (data: Record<string, unknown>) => Promise<boolean>
49
+ /** Create and save a built-in biology template preset as experiment design data. */
50
+ saveTemplatePreset: (
51
+ experimentId: number,
52
+ name: TemplatePresetId | string,
53
+ options?: TemplatePresetCollectionOptions,
54
+ ) => Promise<boolean>
55
+ /** Create and save a built-in biology template preset for the current experiment. */
56
+ saveCurrentTemplatePreset: (
57
+ name: TemplatePresetId | string,
58
+ options?: TemplatePresetCollectionOptions,
59
+ ) => Promise<boolean>
22
60
  /** Save analysis result for an experiment. */
23
61
  saveAnalysis: (experimentId: number, result: Record<string, unknown>) => Promise<boolean>
62
+ /** Save analysis result for the current experiment. */
63
+ saveCurrentAnalysis: (result: Record<string, unknown>) => Promise<boolean>
24
64
  /** Save design and/or analysis in one call. */
25
65
  save: (experimentId: number, opts: {
26
66
  design?: Record<string, unknown>
@@ -28,28 +68,59 @@ export interface UseExperimentSaveReturn {
28
68
  }) => Promise<boolean>
29
69
  /** Load design data for an experiment. */
30
70
  loadDesign: (experimentId: number) => Promise<Record<string, unknown> | null>
71
+ /** Load design data for the current experiment. */
72
+ loadCurrentDesign: () => Promise<Record<string, unknown> | null>
31
73
  /** Load analysis result for an experiment. */
32
74
  loadAnalysis: (experimentId: number) => Promise<Record<string, unknown> | null>
75
+ /** Load analysis result for the current experiment. */
76
+ loadCurrentAnalysis: () => Promise<Record<string, unknown> | null>
33
77
  /** Delete design data for an experiment. */
34
78
  deleteDesign: (experimentId: number) => Promise<boolean>
79
+ /** Delete design data for the current experiment. */
80
+ deleteCurrentDesign: () => Promise<boolean>
35
81
  /** Delete analysis result for an experiment. */
36
82
  deleteAnalysis: (experimentId: number) => Promise<boolean>
83
+ /** Delete analysis result for the current experiment. */
84
+ deleteCurrentAnalysis: () => Promise<boolean>
85
+ /** Require and return the current experiment id, or throw when missing. */
86
+ requireCurrentExperimentId: () => number
37
87
  }
38
88
 
39
- /** Persists and loads experiment design and analysis data via the plugin's API endpoint. */
89
+ /** Persists experiment design, analysis, and built-in template preset data through the platform API. */
40
90
  export function useExperimentSave(
41
91
  options: UseExperimentSaveOptions = {},
42
92
  ): UseExperimentSaveReturn {
43
- const api = useApi({ baseUrl: options.apiBaseUrl })
44
- const pluginId = options.pluginId
93
+ const injectedContext = getInjectedPlatformContext()
94
+ const api = useApi({ baseUrl: options.apiBaseUrl ?? injectedContext?.platformApiUrl })
95
+ const pluginId = options.pluginId ?? injectedContext?.plugin?.id
45
96
  const schemaVersion = options.schemaVersion ?? '1.0'
97
+ const currentExperimentId = computed<number | undefined>(() => {
98
+ return resolveCurrentExperimentId(injectedContext)
99
+ })
100
+ const hasCurrentExperiment = computed(() => currentExperimentId.value !== undefined)
46
101
 
102
+ const request = useRequestSyncState('Experiment request failed.')
47
103
  const isSaving = ref(false)
48
- const error = ref<string | null>(null)
49
- const lastSavedAt = ref<Date | null>(null)
104
+ const isLoading = ref(false)
105
+ const error = request.error
106
+ const lastLoadedAt = request.lastLoadedAt
107
+ const lastSavedAt = request.lastSavedAt
50
108
 
51
- function setError(e: unknown): void {
52
- error.value = e instanceof Error ? e.message : 'Unknown error'
109
+ function requireCurrentExperimentId(): number {
110
+ const id = currentExperimentId.value
111
+ if (id === undefined) {
112
+ throw new Error('[MINT SDK] No current experiment is selected.')
113
+ }
114
+ return id
115
+ }
116
+
117
+ function currentExperimentIdOrError(action: string): number | undefined {
118
+ const id = currentExperimentId.value
119
+ if (id === undefined) {
120
+ request.setError(`No current experiment is selected for ${action}`)
121
+ return undefined
122
+ }
123
+ return id
53
124
  }
54
125
 
55
126
  async function saveDesign(
@@ -57,78 +128,102 @@ export function useExperimentSave(
57
128
  data: Record<string, unknown>,
58
129
  ): Promise<boolean> {
59
130
  if (!pluginId) {
60
- error.value = 'pluginId is required for saveDesign'
131
+ request.setError('pluginId is required for saveDesign')
61
132
  return false
62
133
  }
63
134
  isSaving.value = true
64
- error.value = null
65
135
  try {
66
- await api.put(`/experiments/${experimentId}/data`, {
67
- plugin_id: pluginId,
68
- data,
69
- schema_version: schemaVersion,
70
- })
71
- lastSavedAt.value = new Date()
136
+ await request.run(async () => {
137
+ await api.put(`/experiments/${experimentId}/data`, {
138
+ plugin_id: pluginId,
139
+ data,
140
+ schema_version: schemaVersion,
141
+ })
142
+ }, { success: 'save' })
72
143
  return true
73
- } catch (e) {
74
- setError(e)
144
+ } catch {
75
145
  return false
76
146
  } finally {
77
147
  isSaving.value = false
78
148
  }
79
149
  }
80
150
 
151
+ async function saveCurrentDesign(data: Record<string, unknown>): Promise<boolean> {
152
+ const experimentId = currentExperimentIdOrError('saveDesign')
153
+ return experimentId === undefined ? false : saveDesign(experimentId, data)
154
+ }
155
+
156
+ async function saveTemplatePreset(
157
+ experimentId: number,
158
+ name: TemplatePresetId | string,
159
+ presetOptions: TemplatePresetCollectionOptions = {},
160
+ ): Promise<boolean> {
161
+ const collection = createBioTemplatePresetCollection(name, presetOptions)
162
+ return saveDesign(experimentId, collection as unknown as Record<string, unknown>)
163
+ }
164
+
165
+ async function saveCurrentTemplatePreset(
166
+ name: TemplatePresetId | string,
167
+ presetOptions: TemplatePresetCollectionOptions = {},
168
+ ): Promise<boolean> {
169
+ const experimentId = currentExperimentIdOrError('saveTemplatePreset')
170
+ return experimentId === undefined ? false : saveTemplatePreset(experimentId, name, presetOptions)
171
+ }
172
+
81
173
  async function saveAnalysis(
82
174
  experimentId: number,
83
175
  result: Record<string, unknown>,
84
176
  ): Promise<boolean> {
85
177
  if (!pluginId) {
86
- error.value = 'pluginId is required for saveAnalysis'
178
+ request.setError('pluginId is required for saveAnalysis')
87
179
  return false
88
180
  }
89
181
  isSaving.value = true
90
- error.value = null
91
182
  try {
92
- await api.put(`/experiments/${experimentId}/results/${pluginId}`, {
93
- result,
94
- })
95
- lastSavedAt.value = new Date()
183
+ await request.run(async () => {
184
+ await api.put(`/experiments/${experimentId}/results/${pluginId}`, {
185
+ result,
186
+ })
187
+ }, { success: 'save' })
96
188
  return true
97
- } catch (e) {
98
- setError(e)
189
+ } catch {
99
190
  return false
100
191
  } finally {
101
192
  isSaving.value = false
102
193
  }
103
194
  }
104
195
 
196
+ async function saveCurrentAnalysis(result: Record<string, unknown>): Promise<boolean> {
197
+ const experimentId = currentExperimentIdOrError('saveAnalysis')
198
+ return experimentId === undefined ? false : saveAnalysis(experimentId, result)
199
+ }
200
+
105
201
  async function save(
106
202
  experimentId: number,
107
203
  opts: { design?: Record<string, unknown>; analysis?: Record<string, unknown> },
108
204
  ): Promise<boolean> {
109
205
  if ((opts.design || opts.analysis) && !pluginId) {
110
- error.value = 'pluginId is required for save'
206
+ request.setError('pluginId is required for save')
111
207
  return false
112
208
  }
113
209
  isSaving.value = true
114
- error.value = null
115
210
  try {
116
- if (opts.design) {
117
- await api.put(`/experiments/${experimentId}/data`, {
118
- plugin_id: pluginId,
119
- data: opts.design,
120
- schema_version: schemaVersion,
121
- })
122
- }
123
- if (opts.analysis) {
124
- await api.put(`/experiments/${experimentId}/results/${pluginId}`, {
125
- result: opts.analysis,
126
- })
127
- }
128
- lastSavedAt.value = new Date()
211
+ await request.run(async () => {
212
+ if (opts.design) {
213
+ await api.put(`/experiments/${experimentId}/data`, {
214
+ plugin_id: pluginId,
215
+ data: opts.design,
216
+ schema_version: schemaVersion,
217
+ })
218
+ }
219
+ if (opts.analysis) {
220
+ await api.put(`/experiments/${experimentId}/results/${pluginId}`, {
221
+ result: opts.analysis,
222
+ })
223
+ }
224
+ }, { success: 'save' })
129
225
  return true
130
- } catch (e) {
131
- setError(e)
226
+ } catch {
132
227
  return false
133
228
  } finally {
134
229
  isSaving.value = false
@@ -138,55 +233,112 @@ export function useExperimentSave(
138
233
  async function loadDesign(
139
234
  experimentId: number,
140
235
  ): Promise<Record<string, unknown> | null> {
236
+ isLoading.value = true
141
237
  try {
142
- return await api.get<Record<string, unknown>>(`/experiments/${experimentId}/data`)
238
+ return await request.run(
239
+ () => api.get<Record<string, unknown>>(`/experiments/${experimentId}/data`),
240
+ { success: 'load' },
241
+ )
143
242
  } catch {
144
243
  return null
244
+ } finally {
245
+ isLoading.value = false
145
246
  }
146
247
  }
147
248
 
249
+ async function loadCurrentDesign(): Promise<Record<string, unknown> | null> {
250
+ const experimentId = currentExperimentIdOrError('loadDesign')
251
+ return experimentId === undefined ? null : loadDesign(experimentId)
252
+ }
253
+
148
254
  async function loadAnalysis(
149
255
  experimentId: number,
150
256
  ): Promise<Record<string, unknown> | null> {
151
- if (!pluginId) return null
257
+ if (!pluginId) {
258
+ request.setError('pluginId is required for loadAnalysis')
259
+ return null
260
+ }
261
+ isLoading.value = true
152
262
  try {
153
- return await api.get<Record<string, unknown>>(
154
- `/experiments/${experimentId}/results/${pluginId}`,
263
+ return await request.run(
264
+ () => api.get<Record<string, unknown>>(
265
+ `/experiments/${experimentId}/results/${pluginId}`,
266
+ ),
267
+ { success: 'load' },
155
268
  )
156
269
  } catch {
157
270
  return null
271
+ } finally {
272
+ isLoading.value = false
158
273
  }
159
274
  }
160
275
 
276
+ async function loadCurrentAnalysis(): Promise<Record<string, unknown> | null> {
277
+ const experimentId = currentExperimentIdOrError('loadAnalysis')
278
+ return experimentId === undefined ? null : loadAnalysis(experimentId)
279
+ }
280
+
161
281
  async function deleteDesign(experimentId: number): Promise<boolean> {
282
+ isLoading.value = true
162
283
  try {
163
- await api.delete(`/experiments/${experimentId}/data`)
284
+ await request.run(() => api.delete(`/experiments/${experimentId}/data`))
164
285
  return true
165
286
  } catch {
166
287
  return false
288
+ } finally {
289
+ isLoading.value = false
167
290
  }
168
291
  }
169
292
 
293
+ async function deleteCurrentDesign(): Promise<boolean> {
294
+ const experimentId = currentExperimentIdOrError('deleteDesign')
295
+ return experimentId === undefined ? false : deleteDesign(experimentId)
296
+ }
297
+
170
298
  async function deleteAnalysis(experimentId: number): Promise<boolean> {
171
- if (!pluginId) return false
299
+ if (!pluginId) {
300
+ request.setError('pluginId is required for deleteAnalysis')
301
+ return false
302
+ }
303
+ isLoading.value = true
172
304
  try {
173
- await api.delete(`/experiments/${experimentId}/results/${pluginId}`)
305
+ await request.run(() => api.delete(`/experiments/${experimentId}/results/${pluginId}`))
174
306
  return true
175
307
  } catch {
176
308
  return false
309
+ } finally {
310
+ isLoading.value = false
177
311
  }
178
312
  }
179
313
 
314
+ async function deleteCurrentAnalysis(): Promise<boolean> {
315
+ const experimentId = currentExperimentIdOrError('deleteAnalysis')
316
+ return experimentId === undefined ? false : deleteAnalysis(experimentId)
317
+ }
318
+
180
319
  return {
320
+ currentExperimentId,
321
+ hasCurrentExperiment,
181
322
  isSaving,
323
+ isLoading,
182
324
  error,
325
+ lastLoadedAt,
183
326
  lastSavedAt,
184
327
  saveDesign,
328
+ saveCurrentDesign,
329
+ saveTemplatePreset,
330
+ saveCurrentTemplatePreset,
185
331
  saveAnalysis,
332
+ saveCurrentAnalysis,
186
333
  save,
187
334
  loadDesign,
335
+ loadCurrentDesign,
188
336
  loadAnalysis,
337
+ loadCurrentAnalysis,
189
338
  deleteDesign,
339
+ deleteCurrentDesign,
190
340
  deleteAnalysis,
341
+ deleteCurrentAnalysis,
342
+ requireCurrentExperimentId,
191
343
  }
192
344
  }
@@ -1,5 +1,7 @@
1
- import { ref, reactive, computed, watch, onScopeDispose, type Ref, type ComputedRef } from 'vue'
1
+ import { ref, reactive, computed, watch, type Ref, type ComputedRef } from 'vue'
2
2
  import { useApi } from './useApi'
3
+ import { useDebouncedWatch } from './useDebouncedWatch'
4
+ import { useRequestSyncState } from './useRequestSyncState'
3
5
  import { datePresetToISO } from './experiment-utils'
4
6
  import type {
5
7
  ExperimentSummary,
@@ -28,27 +30,47 @@ export interface UseExperimentSelectorOptions {
28
30
  }
29
31
 
30
32
  export interface UseExperimentSelectorReturn {
33
+ /** Current page of fetched experiment rows. */
31
34
  experiments: Ref<ExperimentSummary[]>
35
+ /** Total matching experiment count reported by the platform. */
32
36
  total: Ref<number>
37
+ /** Currently selected experiment, or null. */
33
38
  selectedExperiment: Ref<ExperimentSummary | null>
39
+ /** Mutable filters used for the next fetch. */
34
40
  filters: ExperimentFilters
41
+ /** Whether experiment rows are currently loading. */
35
42
  isLoading: Ref<boolean>
43
+ /** Error message from the last failed fetch, or null. */
36
44
  error: Ref<string | null>
45
+ /** Timestamp of the last successful experiment fetch, or null. */
46
+ lastLoadedAt: Ref<Date | null>
47
+ /** Current zero-based page index. */
37
48
  page: Ref<number>
49
+ /** Whether another page can be loaded. */
38
50
  hasMore: ComputedRef<boolean>
39
51
  // Sort
52
+ /** Combined sort key in the form `<field>:<asc|desc>`. */
40
53
  sortKey: Ref<string>
41
54
  // Filter options
55
+ /** Available experiment type options for filter UI. */
42
56
  experimentTypes: Ref<ExperimentTypeOption[]>
57
+ /** Available project options for filter UI. */
43
58
  projects: Ref<SelectOption<string>[]>
44
59
  // Grouped view
60
+ /** Fetched experiments grouped by project name. */
45
61
  groupedByProject: ComputedRef<[string, ExperimentSummary[]][]>
46
62
  // Methods
63
+ /** Fetch the current page with the current filters. */
47
64
  fetch: () => Promise<void>
65
+ /** Fetch and append the next page when available. */
48
66
  loadMore: () => Promise<void>
67
+ /** Clear fetched rows and refetch page zero. */
49
68
  reset: () => void
69
+ /** Select an experiment. */
50
70
  select: (experiment: ExperimentSummary) => void
71
+ /** Clear selection and filters. */
51
72
  clear: () => void
73
+ /** Fetch experiment type and project filter options. */
52
74
  fetchFilterOptions: () => Promise<void>
53
75
  }
54
76
 
@@ -63,8 +85,10 @@ export function useExperimentSelector(
63
85
  const experiments = ref<ExperimentSummary[]>([])
64
86
  const total = ref(0)
65
87
  const selectedExperiment = ref<ExperimentSummary | null>(null)
66
- const isLoading = ref(false)
67
- const error = ref<string | null>(null)
88
+ const request = useRequestSyncState('Failed to fetch experiments')
89
+ const isLoading = request.loading
90
+ const error = request.error
91
+ const lastLoadedAt = request.lastLoadedAt
68
92
  const page = ref(0)
69
93
 
70
94
  // Sort: combined key like "created_at:desc"
@@ -94,72 +118,69 @@ export function useExperimentSelector(
94
118
  }
95
119
 
96
120
  async function fetchExperiments(): Promise<void> {
97
- isLoading.value = true
98
- error.value = null
99
121
  try {
100
- const params = new URLSearchParams()
101
- // Priority: explicit option > platform context (single type) > filter dropdown > no filter
102
- const allowedTypes = getPlatformContext()?.allowedExperimentTypes
103
- const effectiveType = experimentType
104
- ?? (allowedTypes?.length === 1 ? allowedTypes[0] : undefined)
105
- ?? filters.experimentType
106
- ?? undefined
107
- if (effectiveType) params.set('experiment_type', effectiveType)
108
- if (filters.status) params.set('status', filters.status)
109
- if (filters.search) params.set('search', filters.search)
110
- if (filters.project) params.set('project', filters.project)
111
-
112
- // Sort params
113
- const { sortBy, sortOrder } = parseSortKey()
114
- params.set('sort_by', sortBy)
115
- params.set('sort_order', sortOrder)
116
-
117
- // Date preset → created_after
118
- if (filters.datePreset) {
119
- params.set('created_after', datePresetToISO(filters.datePreset))
120
- }
122
+ await request.run(async () => {
123
+ const params = new URLSearchParams()
124
+ // Priority: explicit option > platform context (single type) > filter dropdown > no filter
125
+ const allowedTypes = getPlatformContext()?.allowedExperimentTypes
126
+ const effectiveType = experimentType
127
+ ?? (allowedTypes?.length === 1 ? allowedTypes[0] : undefined)
128
+ ?? filters.experimentType
129
+ ?? undefined
130
+ if (effectiveType) params.set('experiment_type', effectiveType)
131
+ if (filters.status) params.set('status', filters.status)
132
+ if (filters.search) params.set('search', filters.search)
133
+ if (filters.project) params.set('project', filters.project)
134
+
135
+ // Sort params
136
+ const { sortBy, sortOrder } = parseSortKey()
137
+ params.set('sort_by', sortBy)
138
+ params.set('sort_order', sortOrder)
139
+
140
+ // Date preset → created_after
141
+ if (filters.datePreset) {
142
+ params.set('created_after', datePresetToISO(filters.datePreset))
143
+ }
121
144
 
122
- params.set('limit', String(limit))
123
- params.set('skip', String(page.value * limit))
145
+ params.set('limit', String(limit))
146
+ params.set('skip', String(page.value * limit))
124
147
 
125
- const query = params.toString()
126
- const base = platformBase ?? ''
127
- const url = `${base}/experiments${query ? `?${query}` : ''}`
128
- const data = await api.get<ExperimentListResponse>(url)
148
+ const query = params.toString()
149
+ const base = platformBase ?? ''
150
+ const url = `${base}/experiments${query ? `?${query}` : ''}`
151
+ const data = await api.get<ExperimentListResponse>(url)
129
152
 
130
- // Client-side filter for multiple allowed types (backend only supports single type)
131
- let filtered = data.experiments
132
- if (!effectiveType && allowedTypes && allowedTypes.length > 1) {
133
- const typeSet = new Set(allowedTypes)
134
- filtered = filtered.filter(e => typeSet.has(e.experiment_type))
135
- }
153
+ // Client-side filter for multiple allowed types (backend only supports single type)
154
+ let filtered = data.experiments
155
+ if (!effectiveType && allowedTypes && allowedTypes.length > 1) {
156
+ const typeSet = new Set(allowedTypes)
157
+ filtered = filtered.filter(e => typeSet.has(e.experiment_type))
158
+ }
136
159
 
137
- if (page.value === 0) {
138
- experiments.value = filtered
139
- } else {
140
- experiments.value = [...experiments.value, ...filtered]
141
- }
142
- // When client-side filtering is active (multiple allowedTypes), we can't
143
- // use data.total since it counts all types. Check if server has more pages.
144
- if (!effectiveType && allowedTypes && allowedTypes.length > 1) {
145
- if (data.experiments.length < limit) {
146
- // Server returned less than a full page — no more data
147
- total.value = experiments.value.length
160
+ if (page.value === 0) {
161
+ experiments.value = filtered
148
162
  } else {
149
- // Might be more pages on the server
150
- total.value = experiments.value.length + 1
163
+ experiments.value = [...experiments.value, ...filtered]
151
164
  }
152
- } else {
153
- total.value = data.total
154
- }
155
- } catch (e) {
156
- error.value = e instanceof Error ? e.message : 'Failed to fetch experiments'
165
+ // When client-side filtering is active (multiple allowedTypes), we can't
166
+ // use data.total since it counts all types. Check if server has more pages.
167
+ if (!effectiveType && allowedTypes && allowedTypes.length > 1) {
168
+ if (data.experiments.length < limit) {
169
+ // Server returned less than a full page no more data
170
+ total.value = experiments.value.length
171
+ } else {
172
+ // Might be more pages on the server
173
+ total.value = experiments.value.length + 1
174
+ }
175
+ } else {
176
+ total.value = data.total
177
+ }
178
+ }, { success: 'load', errorMessage: 'Failed to fetch experiments' })
179
+ } catch {
157
180
  if (page.value === 0) {
158
181
  experiments.value = []
159
182
  total.value = 0
160
183
  }
161
- } finally {
162
- isLoading.value = false
163
184
  }
164
185
  }
165
186
 
@@ -215,6 +236,7 @@ export function useExperimentSelector(
215
236
  filters.datePreset = undefined
216
237
  sortKey.value = 'created_at:desc'
217
238
  page.value = 0
239
+ request.clearError()
218
240
  }
219
241
 
220
242
  // Group experiments by project (client-side)
@@ -237,34 +259,25 @@ export function useExperimentSelector(
237
259
  })
238
260
  })
239
261
 
240
- // Debounced watch on search filter
241
- let debounceTimer: ReturnType<typeof setTimeout> | null = null
242
- watch(
262
+ const searchWatch = useDebouncedWatch(
243
263
  () => filters.search,
244
264
  () => {
245
- if (debounceTimer) clearTimeout(debounceTimer)
246
- debounceTimer = setTimeout(() => {
247
- page.value = 0
248
- fetchExperiments()
249
- }, 300)
265
+ page.value = 0
266
+ fetchExperiments()
250
267
  },
268
+ { delay: 300 },
251
269
  )
252
270
 
253
271
  // Immediate watch on discrete filters and sort (cancel any pending search debounce)
254
272
  watch(
255
273
  () => [filters.status, filters.project, filters.experimentType, filters.datePreset, sortKey.value],
256
274
  () => {
257
- if (debounceTimer) clearTimeout(debounceTimer)
258
- debounceTimer = null
275
+ searchWatch.cancel()
259
276
  page.value = 0
260
277
  fetchExperiments()
261
278
  },
262
279
  )
263
280
 
264
- onScopeDispose(() => {
265
- if (debounceTimer) clearTimeout(debounceTimer)
266
- })
267
-
268
281
  if (immediate) {
269
282
  fetchExperiments()
270
283
  }
@@ -276,6 +289,7 @@ export function useExperimentSelector(
276
289
  filters,
277
290
  isLoading,
278
291
  error,
292
+ lastLoadedAt,
279
293
  page,
280
294
  hasMore,
281
295
  sortKey,