@morscherlab/mint-sdk 1.2.0-beta.2 → 1.2.0-beta.20

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 (493) hide show
  1. package/README.md +65 -5
  2. package/dist/{ExperimentPopover-tLwJ7XF4.js → ExperimentPopover-CJdNlCZb.js} +24 -15
  3. package/dist/ExperimentPopover-CJdNlCZb.js.map +1 -0
  4. package/dist/{ExperimentSelectorModal-DyPAUCLw.js → ExperimentSelectorModal-DCXmwKNS.js} +5 -5
  5. package/dist/ExperimentSelectorModal-DCXmwKNS.js.map +1 -0
  6. package/dist/{SettingsModal-BgKY93zh.js → SettingsModal-BYR20I4c.js} +5 -8
  7. package/dist/SettingsModal-BYR20I4c.js.map +1 -0
  8. package/dist/{EmptyState-p90quq8Z.js → Skeleton-BAF3CKY7.js} +111 -93
  9. package/dist/Skeleton-BAF3CKY7.js.map +1 -0
  10. package/dist/__tests__/components/DataStates.test.d.ts +1 -0
  11. package/dist/__tests__/components/ExperimentTimeline.test.d.ts +1 -0
  12. package/dist/__tests__/components/FileBrowserModal.test.d.ts +1 -0
  13. package/dist/__tests__/components/FilePicker.state.test.d.ts +1 -0
  14. package/dist/__tests__/components/FilePicker.test.d.ts +1 -0
  15. package/dist/__tests__/components/PlotlyChart.test.d.ts +1 -0
  16. package/dist/__tests__/components/PluginWorkspaceView.internal.test.d.ts +1 -0
  17. package/dist/__tests__/components/SecretInput.test.d.ts +1 -0
  18. package/dist/__tests__/components/WellEditPopupInternal.test.d.ts +1 -0
  19. package/dist/__tests__/components/iconButtonIconSizing.test.d.ts +1 -0
  20. package/dist/__tests__/composables/useFileBrowser.test.d.ts +1 -0
  21. package/dist/__tests__/composables/useListReorder.test.d.ts +1 -0
  22. package/dist/__tests__/composables/usePlatformFilePickerAdapter.test.d.ts +1 -0
  23. package/dist/__tests__/composables/usePluginConfig.test.d.ts +15 -0
  24. package/dist/__tests__/styles/cssTokens.test.d.ts +1 -0
  25. package/dist/apiErrors.d.ts +26 -0
  26. package/dist/components/ActionMenu.vue.d.ts +1 -1
  27. package/dist/components/AppAvatarMenu.vue.d.ts +4 -0
  28. package/dist/components/AppPluginSwitcher.vue.d.ts +2 -0
  29. package/dist/components/AppSidebar.vue.d.ts +1 -1
  30. package/dist/components/AuditTrail.vue.d.ts +13 -0
  31. package/dist/components/AutoGroupModal.vue.d.ts +1 -1
  32. package/dist/components/BaseInput.vue.d.ts +1 -1
  33. package/dist/components/BaseSelect.vue.d.ts +1 -1
  34. package/dist/components/BaseTextarea.vue.d.ts +1 -1
  35. package/dist/components/BatchProgressList.vue.d.ts +17 -0
  36. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +4 -4
  37. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +2 -2
  38. package/dist/components/ConcentrationInput.vue.d.ts +1 -1
  39. package/dist/components/DataFrame.vue.d.ts +14 -2
  40. package/dist/components/DatePicker.vue.d.ts +2 -2
  41. package/dist/components/DateTimePicker.vue.d.ts +3 -1
  42. package/dist/components/DropdownButton.vue.d.ts +2 -0
  43. package/dist/components/ExperimentDataViewer.vue.d.ts +26 -1
  44. package/dist/components/ExperimentPopover.vue.d.ts +1 -0
  45. package/dist/components/ExperimentTimeline.vue.d.ts +29 -1
  46. package/dist/components/FileBrowserModal.vue.d.ts +99 -0
  47. package/dist/components/FilePicker.vue.d.ts +32 -0
  48. package/dist/components/FileUploader.vue.d.ts +5 -2
  49. package/dist/components/FitPanel.vue.d.ts +1 -1
  50. package/dist/components/FormulaInput.vue.d.ts +1 -1
  51. package/dist/components/GeneratedPlotlyResult.vue.d.ts +1 -3
  52. package/dist/components/JobsStatusTray.vue.d.ts +9 -2
  53. package/dist/components/LcmsSequenceTable.vue.d.ts +29 -1
  54. package/dist/components/MobileSupportGate.vue.d.ts +1 -1
  55. package/dist/components/MoleculeInput.vue.d.ts +1 -1
  56. package/dist/components/NumberInput.vue.d.ts +1 -1
  57. package/dist/components/PlotlyChart.vue.d.ts +59 -0
  58. package/dist/components/PluginWorkspaceView.props.d.ts +29 -1
  59. package/dist/components/PluginWorkspaceView.vue.d.ts +5 -5
  60. package/dist/components/ProgressBar.vue.d.ts +1 -1
  61. package/dist/components/ProtocolStepEditor.vue.d.ts +1 -0
  62. package/dist/components/ReagentList.vue.d.ts +29 -1
  63. package/dist/components/ResourceCard.vue.d.ts +1 -1
  64. package/dist/components/SampleHierarchyTree.vue.d.ts +29 -1
  65. package/dist/components/SampleLegend.vue.d.ts +1 -1
  66. package/dist/components/SampleSelector.drag.d.ts +13 -0
  67. package/dist/components/SampleSelector.tree.d.ts +2 -0
  68. package/dist/components/SampleSelector.vue.d.ts +35 -2
  69. package/dist/components/ScheduleCalendar.vue.d.ts +20 -0
  70. package/dist/components/SecretInput.vue.d.ts +28 -0
  71. package/dist/components/SequenceInput.vue.d.ts +1 -1
  72. package/dist/components/SettingsModal.vue.d.ts +1 -1
  73. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -1
  74. package/dist/components/StatusIndicator.vue.d.ts +1 -1
  75. package/dist/components/TagsInput.vue.d.ts +1 -1
  76. package/dist/components/TimePicker.vue.d.ts +2 -1
  77. package/dist/components/TimeRangeInput.vue.d.ts +1 -1
  78. package/dist/components/UnitInput.vue.d.ts +1 -1
  79. package/dist/components/WellPlate.geometry.d.ts +15 -0
  80. package/dist/components/WellPlate.interaction.d.ts +13 -0
  81. package/dist/components/WellPlate.vue.d.ts +30 -1
  82. package/dist/components/fileBrowserPresentation.d.ts +10 -0
  83. package/dist/components/index.d.ts +13 -0
  84. package/dist/components/index.js +8 -8
  85. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -0
  86. package/dist/components/internal/DataStateInternal.vue.d.ts +43 -0
  87. package/dist/components/internal/FileBrowserDialogInternal.vue.d.ts +34 -0
  88. package/dist/components/internal/FileBrowserListingInternal.vue.d.ts +25 -0
  89. package/dist/components/internal/FileBrowserToolbarInternal.vue.d.ts +26 -0
  90. package/dist/components/internal/FilePickerPreviewInternal.vue.d.ts +18 -0
  91. package/dist/components/internal/FilePickerTreeInternal.vue.d.ts +31 -0
  92. package/dist/components/internal/WellEditPopupInternal.vue.d.ts +3 -1
  93. package/dist/components/internal/WorkspaceControlsInternal.vue.d.ts +23 -0
  94. package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +70 -0
  95. package/dist/components/internal/WorkspaceSidebarInternal.vue.d.ts +43 -0
  96. package/dist/{components-DkHddRMl.js → components-DIum5hkx.js} +5882 -3200
  97. package/dist/components-DIum5hkx.js.map +1 -0
  98. package/dist/composables/controlSchemaTypes.d.ts +34 -0
  99. package/dist/composables/filePicker/localFiles.d.ts +3 -0
  100. package/dist/composables/filePicker/usePickerNavigation.d.ts +96 -0
  101. package/dist/composables/filePicker/usePickerSelection.d.ts +31 -0
  102. package/dist/composables/filePicker/validation.d.ts +5 -0
  103. package/dist/composables/flattenWorkspaceBindings.d.ts +61 -0
  104. package/dist/composables/index.d.ts +11 -2
  105. package/dist/composables/index.js +10 -10
  106. package/dist/composables/pluginWorkspaceModel.d.ts +164 -0
  107. package/dist/composables/useApi.d.ts +14 -7
  108. package/dist/composables/useAsync.d.ts +5 -1
  109. package/dist/composables/useBioTemplateWorkspace.d.ts +4 -20
  110. package/dist/composables/useCommandHistory.d.ts +5 -1
  111. package/dist/composables/useDropdownState.d.ts +18 -2
  112. package/dist/composables/useEventStream.d.ts +9 -0
  113. package/dist/composables/useFileBrowser.d.ts +55 -0
  114. package/dist/composables/useFilePicker.d.ts +189 -0
  115. package/dist/composables/useFocusTrap.d.ts +21 -0
  116. package/dist/composables/useGeneratedAnalysis.d.ts +3 -1
  117. package/dist/composables/useJobsStatusTray.d.ts +25 -1
  118. package/dist/composables/useListReorder.d.ts +90 -0
  119. package/dist/composables/useMenuKeyboard.d.ts +29 -0
  120. package/dist/composables/useOptimisticMutation.d.ts +5 -1
  121. package/dist/composables/usePlatformFilePickerAdapter.d.ts +11 -0
  122. package/dist/composables/usePluginClient.d.ts +2 -0
  123. package/dist/composables/usePluginConfig.d.ts +27 -0
  124. package/dist/composables/usePluginWorkspace.d.ts +51 -0
  125. package/dist/composables/useRequestSyncState.d.ts +42 -2
  126. package/dist/composables/useResourceCrud.d.ts +5 -1
  127. package/dist/composables/useRovingFocus.d.ts +24 -0
  128. package/dist/composables/useScheduleDrag.d.ts +1 -0
  129. package/dist/composables/useWellPainting.d.ts +5 -1
  130. package/dist/composables/useWellPlateAdapter.d.ts +5 -1
  131. package/dist/composables/useWellPlateValidation.d.ts +5 -1
  132. package/dist/composables/workspaceRecipes.d.ts +188 -0
  133. package/dist/{composables-C_i90sGS.js → composables-mFXsTA4g.js} +502 -86
  134. package/dist/composables-mFXsTA4g.js.map +1 -0
  135. package/dist/{useApi-J6zFkHmn.js → errors-BLuqMee5.js} +179 -16
  136. package/dist/errors-BLuqMee5.js.map +1 -0
  137. package/dist/{experiment-Cq145oEW.js → experiment-CVDRAnqp.js} +3 -8
  138. package/dist/experiment-CVDRAnqp.js.map +1 -0
  139. package/dist/{experiment-utils-CLnzPPq5.js → experiment-utils-CV2Wza3z.js} +70 -51
  140. package/dist/experiment-utils-CV2Wza3z.js.map +1 -0
  141. package/dist/index.d.ts +2 -0
  142. package/dist/index.js +15 -15
  143. package/dist/install.js +2 -7
  144. package/dist/install.js.map +1 -1
  145. package/dist/permissions.d.ts +42 -1
  146. package/dist/permissions.js +20 -10
  147. package/dist/permissions.js.map +1 -1
  148. package/dist/stores/auth.d.ts +6 -0
  149. package/dist/stores/index.d.ts +1 -1
  150. package/dist/stores/index.js +3 -3
  151. package/dist/stores/settings.d.ts +31 -1
  152. package/dist/styles.css +15135 -32397
  153. package/dist/templates/builderReadUtils.d.ts +1 -1
  154. package/dist/templates/index.js +2 -4
  155. package/dist/{templates-DsP51i-S.js → templates-CmGfmBRA.js} +303 -106
  156. package/dist/templates-CmGfmBRA.js.map +1 -0
  157. package/dist/types/auth.d.ts +1 -0
  158. package/dist/types/fileBrowserTypes.d.ts +77 -0
  159. package/dist/types/filePicker.d.ts +77 -0
  160. package/dist/types/form-builder.d.ts +1 -1
  161. package/dist/types/index.d.ts +2 -0
  162. package/dist/{useControlSchema-BDx2Olha.js → useControlSchema-DldpxNOP.js} +499 -1162
  163. package/dist/useControlSchema-DldpxNOP.js.map +1 -0
  164. package/dist/{useDropdownState-DSb3BC3N.js → useDropdownState-C7hRiVQf.js} +22 -6
  165. package/dist/useDropdownState-C7hRiVQf.js.map +1 -0
  166. package/dist/{useExperimentSelector-CV4iNfMS.js → useExperimentSelector-CEcIaueJ.js} +4 -4
  167. package/dist/{useExperimentSelector-CV4iNfMS.js.map → useExperimentSelector-CEcIaueJ.js.map} +1 -1
  168. package/dist/{useFormBuilder-BLxG3BOX.js → useFormBuilder-z5L_dUIy.js} +2 -2
  169. package/dist/{useFormBuilder-BLxG3BOX.js.map → useFormBuilder-z5L_dUIy.js.map} +1 -1
  170. package/dist/utils/color.d.ts +13 -0
  171. package/dist/utils/errors.d.ts +9 -0
  172. package/dist/utils/pluginSecrets.d.ts +53 -0
  173. package/dist/utils/records.d.ts +4 -0
  174. package/dist/{useProtocolTemplates-CS_SQl6U.js → validation-uwoJGEb6.js} +811 -212
  175. package/dist/validation-uwoJGEb6.js.map +1 -0
  176. package/package.json +1 -1
  177. package/src/__tests__/apiErrors.test.ts +81 -0
  178. package/src/__tests__/components/ActionMenu.test.ts +94 -0
  179. package/src/__tests__/components/AppAvatarMenu.test.ts +123 -0
  180. package/src/__tests__/components/BaseTabs.test.ts +67 -0
  181. package/src/__tests__/components/DataFrame.test.ts +183 -11
  182. package/src/__tests__/components/DataStates.test.ts +176 -0
  183. package/src/__tests__/components/DatePicker.test.ts +76 -0
  184. package/src/__tests__/components/DateTimePicker.test.ts +36 -0
  185. package/src/__tests__/components/DropdownButton.test.ts +134 -0
  186. package/src/__tests__/components/ExperimentPopover.test.ts +30 -0
  187. package/src/__tests__/components/ExperimentTimeline.test.ts +63 -0
  188. package/src/__tests__/components/FileBrowserModal.test.ts +460 -0
  189. package/src/__tests__/components/FilePicker.state.test.ts +287 -0
  190. package/src/__tests__/components/FilePicker.test.ts +487 -0
  191. package/src/__tests__/components/GeneratedPlotlyResult.test.ts +52 -5
  192. package/src/__tests__/components/GroupAssigner.test.ts +53 -0
  193. package/src/__tests__/components/LcmsSequenceTable.test.ts +22 -0
  194. package/src/__tests__/components/PlotlyChart.test.ts +305 -0
  195. package/src/__tests__/components/PluginWorkspaceView.internal.test.ts +230 -0
  196. package/src/__tests__/components/RackEditor.test.ts +35 -0
  197. package/src/__tests__/components/ReagentList.test.ts +64 -2
  198. package/src/__tests__/components/RetiredStyles.test.ts +31 -0
  199. package/src/__tests__/components/SampleSelector.drag.test.ts +51 -1
  200. package/src/__tests__/components/ScheduleCalendar.test.ts +63 -0
  201. package/src/__tests__/components/SecretInput.test.ts +87 -0
  202. package/src/__tests__/components/SegmentedControl.test.ts +107 -0
  203. package/src/__tests__/components/TimePicker.test.ts +61 -0
  204. package/src/__tests__/components/WellEditPopupInternal.test.ts +108 -0
  205. package/src/__tests__/components/WellPlate.interaction.test.ts +59 -0
  206. package/src/__tests__/components/iconButtonIconSizing.test.ts +18 -0
  207. package/src/__tests__/composables/useApi.test.ts +92 -9
  208. package/src/__tests__/composables/useAuth.test.ts +2 -0
  209. package/src/__tests__/composables/useDropdownState.test.ts +58 -1
  210. package/src/__tests__/composables/useFileBrowser.test.ts +305 -0
  211. package/src/__tests__/composables/useGeneratedAnalysis.test.ts +7 -2
  212. package/src/__tests__/composables/useListReorder.test.ts +145 -0
  213. package/src/__tests__/composables/usePlatformFilePickerAdapter.test.ts +67 -0
  214. package/src/__tests__/composables/usePluginClient.test.ts +143 -0
  215. package/src/__tests__/composables/usePluginConfig.test.ts +497 -0
  216. package/src/__tests__/composables/useRequestSyncState.test.ts +140 -0
  217. package/src/__tests__/docs/frontendDocsCatalog.test.ts +39 -7
  218. package/src/__tests__/stores/settings.test.ts +71 -1
  219. package/src/__tests__/styles/cssTokens.test.ts +131 -0
  220. package/src/__tests__/utils/permissions.test.ts +68 -0
  221. package/src/apiErrors.ts +85 -0
  222. package/src/components/ActionMenu.vue +45 -88
  223. package/src/components/AlertBox.vue +0 -4
  224. package/src/components/AppAvatarMenu.vue +26 -7
  225. package/src/components/AppLayout.vue +0 -4
  226. package/src/components/AppPluginSwitcher.story.vue +87 -0
  227. package/src/components/AppPluginSwitcher.vue +2 -5
  228. package/src/components/AppSidebar.vue +0 -4
  229. package/src/components/AppToastContainer.vue +6 -8
  230. package/src/components/AppTopBar.vue +0 -4
  231. package/src/components/ArtifactSaveDialog.vue +0 -4
  232. package/src/components/ArtifactSelectorModal.vue +0 -4
  233. package/src/components/AuditTrail.story.vue +18 -0
  234. package/src/components/AuditTrail.vue +27 -7
  235. package/src/components/AutoGroupModal.vue +0 -4
  236. package/src/components/Avatar.vue +0 -4
  237. package/src/components/BaseButton.vue +0 -4
  238. package/src/components/BaseCheckbox.vue +0 -4
  239. package/src/components/BaseInput.vue +0 -4
  240. package/src/components/BaseModal.vue +8 -60
  241. package/src/components/BasePill.vue +0 -4
  242. package/src/components/BaseRadioGroup.vue +0 -4
  243. package/src/components/BaseSelect.vue +0 -4
  244. package/src/components/BaseSlider.vue +0 -4
  245. package/src/components/BaseTabs.vue +26 -5
  246. package/src/components/BaseTextarea.vue +0 -4
  247. package/src/components/BaseToggle.vue +0 -4
  248. package/src/components/BatchProgressList.story.vue +18 -0
  249. package/src/components/BatchProgressList.vue +32 -4
  250. package/src/components/BioTemplateExperimentWorkspaceView.vue +10 -21
  251. package/src/components/BioTemplatePackWorkspaceView.vue +11 -15
  252. package/src/components/BioTemplatePresetWorkspaceView.vue +8 -13
  253. package/src/components/Breadcrumb.vue +0 -4
  254. package/src/components/Calendar.vue +0 -4
  255. package/src/components/ChartContainer.story.vue +1 -1
  256. package/src/components/ChartContainer.vue +0 -4
  257. package/src/components/ChemicalFormula.vue +0 -4
  258. package/src/components/CollapsibleCard.vue +0 -4
  259. package/src/components/ColorSlider.vue +0 -4
  260. package/src/components/ComponentBindingRenderer.vue +1 -3
  261. package/src/components/ConcentrationInput.vue +0 -4
  262. package/src/components/ConfirmDialog.vue +0 -4
  263. package/src/components/ControlWorkspaceView.vue +11 -22
  264. package/src/components/DataFrame.story.vue +14 -0
  265. package/src/components/DataFrame.vue +97 -23
  266. package/src/components/DatePicker.vue +73 -27
  267. package/src/components/DateTimePicker.vue +45 -17
  268. package/src/components/Divider.vue +0 -4
  269. package/src/components/DoseCalculator.vue +0 -4
  270. package/src/components/DoseDesignWorkspaceView.vue +6 -25
  271. package/src/components/DropdownButton.vue +24 -6
  272. package/src/components/EmptyState.vue +0 -4
  273. package/src/components/ExperimentCodeBadge.vue +0 -4
  274. package/src/components/ExperimentDataViewer.story.vue +3 -0
  275. package/src/components/ExperimentDataViewer.vue +33 -13
  276. package/src/components/ExperimentPopover.vue +12 -7
  277. package/src/components/ExperimentSelectorModal.vue +0 -4
  278. package/src/components/ExperimentTimeline.story.vue +18 -0
  279. package/src/components/ExperimentTimeline.vue +81 -75
  280. package/src/components/FileBrowserModal.story.vue +203 -0
  281. package/src/components/FileBrowserModal.vue +384 -0
  282. package/src/components/FilePicker.story.vue +44 -0
  283. package/src/components/FilePicker.vue +284 -0
  284. package/src/components/FileUploader.vue +32 -18
  285. package/src/components/FitPanel.vue +0 -4
  286. package/src/components/FormActions.story.vue +96 -0
  287. package/src/components/FormActions.vue +0 -4
  288. package/src/components/FormBuilder.vue +0 -4
  289. package/src/components/FormField.vue +0 -4
  290. package/src/components/FormulaInput.vue +0 -4
  291. package/src/components/GeneratedPathInput.story.vue +68 -0
  292. package/src/components/GeneratedPlotlyResult.story.vue +96 -0
  293. package/src/components/GeneratedPlotlyResult.vue +34 -69
  294. package/src/components/GeneratedPluginView.story.vue +145 -0
  295. package/src/components/GeneratedPluginView.vue +10 -10
  296. package/src/components/GeneratedResultRenderer.story.vue +117 -0
  297. package/src/components/GeneratedResultRenderer.vue +4 -9
  298. package/src/components/GroupAssigner.vue +67 -5
  299. package/src/components/IconButton.vue +0 -4
  300. package/src/components/InstrumentAlertLog.vue +0 -4
  301. package/src/components/InstrumentStateBadge.vue +0 -4
  302. package/src/components/InstrumentStatusCard.vue +0 -4
  303. package/src/components/JobsStatusTray.vue +16 -5
  304. package/src/components/LcmsSequenceTable.vue +81 -50
  305. package/src/components/LoadingSpinner.vue +0 -4
  306. package/src/components/MobileSupportGate.vue +0 -4
  307. package/src/components/MoleculeInput.vue +0 -4
  308. package/src/components/MultiSelect.vue +0 -4
  309. package/src/components/NumberInput.vue +0 -4
  310. package/src/components/PlateMapEditor.vue +3 -14
  311. package/src/components/PlotlyChart.story.vue +61 -0
  312. package/src/components/PlotlyChart.vue +233 -0
  313. package/src/components/PluginIcon.vue +0 -4
  314. package/src/components/PluginWorkspaceView.props.ts +38 -0
  315. package/src/components/PluginWorkspaceView.vue +53 -224
  316. package/src/components/ProgressBar.vue +0 -4
  317. package/src/components/ProtocolStepEditor.vue +2 -4
  318. package/src/components/RackEditor.vue +51 -70
  319. package/src/components/ReagentEditor.vue +0 -4
  320. package/src/components/ReagentList.story.vue +18 -0
  321. package/src/components/ReagentList.vue +127 -82
  322. package/src/components/ResourceCard.vue +0 -4
  323. package/src/components/RuntimeUpdateNotice.story.vue +49 -0
  324. package/src/components/RuntimeUpdateNotice.vue +4 -4
  325. package/src/components/SampleHierarchyTree.story.vue +18 -0
  326. package/src/components/SampleHierarchyTree.vue +32 -9
  327. package/src/components/SampleLegend.vue +2 -8
  328. package/src/components/SampleSelector.drag.ts +51 -0
  329. package/src/components/SampleSelector.story.vue +6 -0
  330. package/src/components/SampleSelector.tree.ts +2 -0
  331. package/src/components/SampleSelector.vue +49 -10
  332. package/src/components/SampleSelectorGroupNode.vue +19 -0
  333. package/src/components/ScheduleCalendar.story.vue +6 -0
  334. package/src/components/ScheduleCalendar.vue +82 -7
  335. package/src/components/ScientificNumber.vue +0 -4
  336. package/src/components/SecretInput.story.vue +79 -0
  337. package/src/components/SecretInput.vue +150 -0
  338. package/src/components/SectionCard.vue +0 -4
  339. package/src/components/SegmentedControl.vue +50 -7
  340. package/src/components/SequenceInput.vue +0 -4
  341. package/src/components/SequenceProgressBar.story.vue +110 -0
  342. package/src/components/SequenceProgressBar.vue +0 -4
  343. package/src/components/SettingsModal.vue +0 -4
  344. package/src/components/Skeleton.vue +0 -4
  345. package/src/components/SmartGroupFieldRecipe.vue +8 -6
  346. package/src/components/SmartGroupManual.vue +0 -4
  347. package/src/components/StatusIndicator.vue +0 -4
  348. package/src/components/StepWizard.vue +0 -4
  349. package/src/components/TagsInput.vue +4 -6
  350. package/src/components/ThemeToggle.vue +0 -4
  351. package/src/components/TimePicker.vue +36 -7
  352. package/src/components/TimeRangeInput.vue +0 -4
  353. package/src/components/Tooltip.story.vue +3 -3
  354. package/src/components/Tooltip.vue +0 -4
  355. package/src/components/UnitInput.vue +0 -4
  356. package/src/components/WellPlate.geometry.ts +60 -1
  357. package/src/components/WellPlate.interaction.ts +44 -0
  358. package/src/components/WellPlate.story.vue +8 -0
  359. package/src/components/WellPlate.vue +53 -7
  360. package/src/components/fileBrowserPresentation.ts +39 -0
  361. package/src/components/index.ts +14 -0
  362. package/src/components/internal/AppTopBarPageSelectorInternal.vue +2 -5
  363. package/src/components/internal/AppTopBarPillNavInternal.vue +0 -4
  364. package/src/components/internal/DataStateInternal.vue +74 -0
  365. package/src/components/internal/FileBrowserDialogInternal.vue +75 -0
  366. package/src/components/internal/FileBrowserListingInternal.vue +197 -0
  367. package/src/components/internal/FileBrowserToolbarInternal.vue +136 -0
  368. package/src/components/internal/FilePickerPreviewInternal.vue +100 -0
  369. package/src/components/internal/FilePickerTreeInternal.vue +238 -0
  370. package/src/components/internal/FormSectionRenderer.vue +0 -4
  371. package/src/components/internal/WellEditPopupInternal.vue +20 -5
  372. package/src/components/internal/WorkspaceControlsInternal.vue +20 -0
  373. package/src/components/internal/WorkspaceShellInternal.vue +125 -0
  374. package/src/components/internal/WorkspaceSidebarInternal.vue +82 -0
  375. package/src/composables/controlSchemaTypes.ts +34 -0
  376. package/src/composables/experimentDesignData.ts +1 -3
  377. package/src/composables/filePicker/localFiles.ts +58 -0
  378. package/src/composables/filePicker/usePickerNavigation.ts +220 -0
  379. package/src/composables/filePicker/usePickerSelection.ts +259 -0
  380. package/src/composables/filePicker/validation.ts +41 -0
  381. package/src/composables/flattenWorkspaceBindings.ts +104 -0
  382. package/src/composables/formBuilderRegistry.ts +5 -0
  383. package/src/composables/index.ts +50 -0
  384. package/src/composables/pluginWorkspaceModel.ts +182 -0
  385. package/src/composables/useApi.ts +57 -13
  386. package/src/composables/useAsync.ts +5 -1
  387. package/src/composables/useAuth.ts +3 -0
  388. package/src/composables/useBioTemplatePackWorkspace.ts +15 -9
  389. package/src/composables/useBioTemplatePresetWorkspace.ts +14 -15
  390. package/src/composables/useBioTemplateWorkspace.ts +18 -43
  391. package/src/composables/useCommandHistory.ts +5 -1
  392. package/src/composables/useDropdownState.ts +35 -5
  393. package/src/composables/useEventStream.ts +14 -2
  394. package/src/composables/useFileBrowser.ts +314 -0
  395. package/src/composables/useFileImport.ts +2 -7
  396. package/src/composables/useFilePicker.ts +366 -0
  397. package/src/composables/useFocusTrap.ts +91 -0
  398. package/src/composables/useGeneratedAnalysis.ts +32 -27
  399. package/src/composables/useJobsStatusTray.ts +184 -17
  400. package/src/composables/useListReorder.story.vue +143 -0
  401. package/src/composables/useListReorder.ts +242 -0
  402. package/src/composables/useMenuKeyboard.ts +90 -0
  403. package/src/composables/useOptimisticMutation.ts +6 -16
  404. package/src/composables/usePlatformFilePickerAdapter.ts +104 -0
  405. package/src/composables/usePluginClient.ts +50 -4
  406. package/src/composables/usePluginConfig.ts +252 -0
  407. package/src/composables/usePluginWorkspace.ts +208 -0
  408. package/src/composables/useRackEditor.ts +2 -11
  409. package/src/composables/useRequestSyncState.ts +98 -28
  410. package/src/composables/useResourceCrud.ts +6 -16
  411. package/src/composables/useRovingFocus.ts +66 -0
  412. package/src/composables/useScheduleDrag.ts +37 -0
  413. package/src/composables/useWellPainting.ts +10 -30
  414. package/src/composables/useWellPlateAdapter.ts +5 -1
  415. package/src/composables/useWellPlateEditor.ts +1 -11
  416. package/src/composables/useWellPlateValidation.ts +5 -1
  417. package/src/composables/workspaceRecipes.ts +267 -0
  418. package/src/index.ts +19 -0
  419. package/src/permissions.ts +48 -9
  420. package/src/stores/auth.ts +1 -0
  421. package/src/stores/experiment.ts +2 -7
  422. package/src/stores/index.ts +9 -1
  423. package/src/stores/settings.ts +93 -1
  424. package/src/styles/components/audit-trail.css +5 -5
  425. package/src/styles/components/batch-progress-list.css +7 -0
  426. package/src/styles/components/color-slider.css +2 -2
  427. package/src/styles/components/data-state.css +30 -0
  428. package/src/styles/components/dataframe.css +29 -35
  429. package/src/styles/components/experiment-data-viewer.css +4 -4
  430. package/src/styles/components/experiment-timeline.css +3 -30
  431. package/src/styles/components/file-browser-modal.css +489 -0
  432. package/src/styles/components/file-picker.css +502 -0
  433. package/src/styles/components/form-builder.css +5 -5
  434. package/src/styles/components/icon-button.css +9 -0
  435. package/src/styles/components/lcms-sequence-table.css +16 -1
  436. package/src/styles/components/multi-select.css +1 -1
  437. package/src/styles/components/reagent-list.css +38 -31
  438. package/src/styles/components/sample-hierarchy-tree.css +5 -19
  439. package/src/styles/components/sample-selector.css +9 -2
  440. package/src/styles/components/schedule-calendar.css +8 -0
  441. package/src/styles/components/scientific-number.css +1 -1
  442. package/src/styles/components/secret-input.css +60 -0
  443. package/src/styles/components/well-plate.css +8 -0
  444. package/src/styles/index.css +12 -0
  445. package/src/styles/variables.css +29 -0
  446. package/src/templates/builderPresetControls.ts +1 -1
  447. package/src/templates/builderReadUtils.ts +1 -3
  448. package/src/templates/componentPlateHelpers.ts +3 -1
  449. package/src/templates/componentTargetResolvers.ts +1 -3
  450. package/src/templates/controlSchemaTargets.ts +1 -3
  451. package/src/templates/doseResponseAdapters.ts +2 -13
  452. package/src/templates/elisaAssayCollectionBuilder.ts +2 -2
  453. package/src/templates/plateMapBuilder.ts +2 -2
  454. package/src/templates/qpcrPlateBuilder.ts +1 -1
  455. package/src/templates/templateValidators.ts +1 -1
  456. package/src/templates/timeCourseBuilder.ts +2 -2
  457. package/src/templates/wellPlateScreenCollectionBuilder.ts +2 -2
  458. package/src/types/auth.ts +1 -0
  459. package/src/types/fileBrowserTypes.ts +85 -0
  460. package/src/types/filePicker.ts +72 -0
  461. package/src/types/form-builder.ts +1 -0
  462. package/src/types/index.ts +14 -0
  463. package/src/utils/color.ts +51 -0
  464. package/src/utils/errors.ts +29 -0
  465. package/src/utils/formModelSync.ts +1 -3
  466. package/src/utils/pluginIcon.ts +1 -3
  467. package/src/utils/pluginSecrets.ts +102 -0
  468. package/src/utils/records.ts +9 -0
  469. package/dist/EmptyState-p90quq8Z.js.map +0 -1
  470. package/dist/ExperimentPopover-tLwJ7XF4.js.map +0 -1
  471. package/dist/ExperimentSelectorModal-DyPAUCLw.js.map +0 -1
  472. package/dist/SettingsModal-BgKY93zh.js.map +0 -1
  473. package/dist/components-DkHddRMl.js.map +0 -1
  474. package/dist/composables/autoGroupConstants.d.ts +0 -1
  475. package/dist/composables/autoGroupGrouping.d.ts +0 -3
  476. package/dist/composables/useAutoGroupInputSources.d.ts +0 -32
  477. package/dist/composables-C_i90sGS.js.map +0 -1
  478. package/dist/experiment-Cq145oEW.js.map +0 -1
  479. package/dist/experiment-utils-CLnzPPq5.js.map +0 -1
  480. package/dist/templates/builderDefaults.d.ts +0 -1
  481. package/dist/templates/plateGeometry.d.ts +0 -4
  482. package/dist/templates-DsP51i-S.js.map +0 -1
  483. package/dist/useApi-J6zFkHmn.js.map +0 -1
  484. package/dist/useControlSchema-BDx2Olha.js.map +0 -1
  485. package/dist/useDropdownState-DSb3BC3N.js.map +0 -1
  486. package/dist/useProtocolTemplates-CS_SQl6U.js.map +0 -1
  487. package/src/__tests__/composables/useAutoGroupInputSources.test.ts +0 -127
  488. package/src/composables/autoGroupConstants.ts +0 -4
  489. package/src/composables/autoGroupGrouping.ts +0 -148
  490. package/src/composables/useAutoGroupInputSources.ts +0 -147
  491. package/src/templates/builderDefaults.ts +0 -11
  492. package/src/templates/plateGeometry.ts +0 -62
  493. /package/dist/__tests__/{composables/useAutoGroupInputSources.test.d.ts → apiErrors.test.d.ts} +0 -0
@@ -15,6 +15,7 @@ import {
15
15
  import { usePlatformContext } from './usePlatformContext'
16
16
  import { useRequestSyncState } from './useRequestSyncState'
17
17
  import { useAuthStore } from '../stores/auth'
18
+ import { PLUGIN_SECRET_FORMAT_KEY } from '../utils/pluginSecrets'
18
19
  import {
19
20
  currentExperimentFromContext,
20
21
  getInjectedPlatformContext,
@@ -422,6 +423,7 @@ export function createPluginClient<TClient extends object = GeneratedPluginClien
422
423
  const api = useApi({
423
424
  baseUrl: parts.baseUrl,
424
425
  withAuth: endpoint.requiresAuth !== false,
426
+ typedErrors: true,
425
427
  })
426
428
  const config = endpointRequestConfig(endpoint, parts.query, callOptions)
427
429
 
@@ -468,6 +470,7 @@ export async function uploadPluginEndpoint<TResponse = unknown>(
468
470
  const api = useApi({
469
471
  baseUrl: parts.baseUrl,
470
472
  withAuth: endpoint.requiresAuth !== false,
473
+ typedErrors: true,
471
474
  })
472
475
  const body = endpoint.hasBody
473
476
  ? requestBodyFromPayload(payload, parts.requestPayload, endpoint)
@@ -506,6 +509,7 @@ export async function downloadPluginEndpoint(
506
509
  const api = useApi({
507
510
  baseUrl: parts.baseUrl,
508
511
  withAuth: endpoint.requiresAuth !== false,
512
+ typedErrors: true,
509
513
  })
510
514
  const config = {
511
515
  ...(endpointRequestConfig(endpoint, parts.query, { signal: options.signal }) ?? {}),
@@ -538,8 +542,33 @@ function normalizePluginSettingsOptions(options: UsePluginSettingsOptions | stri
538
542
  return typeof options === 'string' ? { pluginName: options } : options
539
543
  }
540
544
 
545
+ /** @deprecated Use `useGeneratedPluginClient()` for generated clients. */
546
+ export function usePluginClient<TClient>(client: TClient): TClient {
547
+ return client
548
+ }
549
+
541
550
  const PLATFORM_OWNED_SETTINGS_KEY = 'allowed_experiment_types'
542
551
 
552
+ /**
553
+ * Secret settings travel as `{ "$secret": "<id>" }` references, so an untouched
554
+ * secret round-trips without ever exposing its value. Declaring the format on
555
+ * every write keeps a `null` unambiguous: it clears the stored secret instead of
556
+ * meaning "unchanged", which is what the deprecated placeholder form meant.
557
+ */
558
+ function withSecretFormat(
559
+ payload: Record<string, unknown>,
560
+ secretFormat: string | null,
561
+ ): Record<string, unknown> {
562
+ if (!secretFormat) return payload
563
+ return { ...payload, [PLUGIN_SECRET_FORMAT_KEY]: secretFormat }
564
+ }
565
+
566
+ function responseSecretFormat(response: unknown): string | null {
567
+ if (!response || typeof response !== 'object') return null
568
+ const format = (response as Record<string, unknown>)[PLUGIN_SECRET_FORMAT_KEY]
569
+ return typeof format === 'string' && format ? format : null
570
+ }
571
+
543
572
  function cloneRecord(value: Record<string, unknown>): Record<string, unknown> {
544
573
  return JSON.parse(JSON.stringify(value)) as Record<string, unknown>
545
574
  }
@@ -613,6 +642,7 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
613
642
  const values = ref({}) as Ref<PluginSettingsValues<TSettings>>
614
643
  const savedValues = shallowRef<Record<string, unknown>>({})
615
644
  const revision = ref<string | null>(null)
645
+ const secretFormat = ref<string | null>(null)
616
646
  const request = useRequestSyncState('Plugin settings request failed.')
617
647
  const isLoading = ref(false)
618
648
  const isSaving = ref(false)
@@ -645,11 +675,16 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
645
675
  values.value = cloneRecord(nextValues) as PluginSettingsValues<TSettings>
646
676
  }
647
677
 
648
- function applyLoaded(nextValues: Record<string, unknown>, nextRevision: string | null): void {
678
+ function applyLoaded(
679
+ nextValues: Record<string, unknown>,
680
+ nextRevision: string | null,
681
+ nextSecretFormat: string | null = secretFormat.value,
682
+ ): void {
649
683
  const cloned = cloneRecord(nextValues)
650
684
  values.value = cloned as PluginSettingsValues<TSettings>
651
685
  savedValues.value = cloneRecord(cloned)
652
686
  revision.value = nextRevision
687
+ secretFormat.value = nextSecretFormat
653
688
  }
654
689
 
655
690
  async function load(): Promise<void> {
@@ -669,6 +704,7 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
669
704
  applyLoaded(
670
705
  responseSettingsPayload(response),
671
706
  responseSettingsRevision(response),
707
+ responseSecretFormat(response),
672
708
  )
673
709
  } catch {
674
710
  // Error state is handled by request.run().
@@ -702,7 +738,12 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
702
738
  revision?: string
703
739
  }>(
704
740
  `/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`,
705
- { config: topLevelSettingsPatch(payload, typedKeys) },
741
+ {
742
+ config: withSecretFormat(
743
+ topLevelSettingsPatch(payload, typedKeys),
744
+ secretFormat.value,
745
+ ),
746
+ },
706
747
  ),
707
748
  { success: 'save', errorMessage: 'Failed to save plugin settings' },
708
749
  )
@@ -725,7 +766,11 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
725
766
  }
726
767
 
727
768
  if (!response) return true
728
- applyLoaded(responseSettingsPayload(response), nextRevision)
769
+ applyLoaded(
770
+ responseSettingsPayload(response),
771
+ nextRevision,
772
+ responseSecretFormat(response) ?? secretFormat.value,
773
+ )
729
774
  return true
730
775
  } else {
731
776
  const ifMatch = revision.value
@@ -736,13 +781,14 @@ export function usePluginSettings<TSettings = Record<string, unknown>>(
736
781
  settings: Record<string, unknown>
737
782
  mode?: string
738
783
  revision?: string
739
- }>('/settings', payload, ifMatch),
784
+ }>('/settings', withSecretFormat(payload, secretFormat.value), ifMatch),
740
785
  { success: 'save', errorMessage: 'Failed to save plugin settings' },
741
786
  )
742
787
  }
743
788
  applyLoaded(
744
789
  responseSettingsPayload(response),
745
790
  responseSettingsRevision(response) ?? revision.value,
791
+ responseSecretFormat(response) ?? secretFormat.value,
746
792
  )
747
793
  return true
748
794
  } catch {
@@ -0,0 +1,252 @@
1
+ import { ref, computed, onMounted, type Ref, type ComputedRef } from 'vue'
2
+ import { useApi } from './useApi'
3
+ import { usePlatformContext } from './usePlatformContext'
4
+ import { getInjectedPlatformContext } from './platformContextHelpers'
5
+ import { useRequestSyncState } from './useRequestSyncState'
6
+
7
+ const PLATFORM_OWNED_SETTINGS_KEY = 'allowed_experiment_types'
8
+
9
+ function cloneConfig(config: Record<string, unknown>): Record<string, unknown> {
10
+ return JSON.parse(JSON.stringify(config)) as Record<string, unknown>
11
+ }
12
+
13
+ function hasOwnConfig(config: Record<string, unknown>, key: string): boolean {
14
+ return Object.prototype.hasOwnProperty.call(config, key)
15
+ }
16
+
17
+ function changedTopLevelKeys(
18
+ current: Record<string, unknown>,
19
+ saved: Record<string, unknown>,
20
+ ): Set<string> {
21
+ return new Set(
22
+ [...new Set([...Object.keys(current), ...Object.keys(saved)])].filter(
23
+ key => JSON.stringify(current[key]) !== JSON.stringify(saved[key]),
24
+ ),
25
+ )
26
+ }
27
+
28
+ function topLevelConfigPatch(
29
+ current: Record<string, unknown>,
30
+ keys: Iterable<string>,
31
+ ): Record<string, unknown> {
32
+ return Object.fromEntries(
33
+ [...keys].map(key => [
34
+ key,
35
+ hasOwnConfig(current, key) ? current[key] : null,
36
+ ]),
37
+ )
38
+ }
39
+
40
+ function platformOwnedConfigPatch(config: Record<string, unknown>): Record<string, unknown> {
41
+ return {
42
+ [PLATFORM_OWNED_SETTINGS_KEY]: hasOwnConfig(config, PLATFORM_OWNED_SETTINGS_KEY)
43
+ ? config[PLATFORM_OWNED_SETTINGS_KEY]
44
+ : null,
45
+ }
46
+ }
47
+
48
+ function responseConfigPayload(response: {
49
+ config?: Record<string, unknown>
50
+ settings?: Record<string, unknown>
51
+ }): Record<string, unknown> {
52
+ return response.config ?? response.settings ?? {}
53
+ }
54
+
55
+ export interface UsePluginConfigReturn {
56
+ /** Editable plugin configuration values. */
57
+ config: Ref<Record<string, unknown>>
58
+ /** Whether the config is currently loading. */
59
+ isLoading: Ref<boolean>
60
+ /** Whether the config is currently saving. */
61
+ isSaving: Ref<boolean>
62
+ /** Error message from the last failed operation, or null. */
63
+ error: Ref<string | null>
64
+ /** Timestamp of the last successful load, or null. */
65
+ lastLoadedAt: Ref<Date | null>
66
+ /** Timestamp of the last successful save, or null. */
67
+ lastSavedAt: Ref<Date | null>
68
+ /** Whether local config differs from the last loaded or saved snapshot. */
69
+ isDirty: ComputedRef<boolean>
70
+ /** Load plugin configuration from the platform. */
71
+ load: () => Promise<void>
72
+ /** Save plugin configuration to the platform. */
73
+ save: () => Promise<boolean>
74
+ /** Restore the last loaded or saved configuration snapshot. */
75
+ reset: () => void
76
+ }
77
+
78
+ /**
79
+ * @deprecated Unused by the MINT platform and scheduled for removal in MINT 1.3; use `usePluginSettings()` for typed plugin settings.
80
+ */
81
+ export function usePluginConfig(pluginName?: string): UsePluginConfigReturn {
82
+ const injectedContext = getInjectedPlatformContext()
83
+ const { plugin } = usePlatformContext()
84
+ const platformApi = useApi()
85
+ const pluginApi = useApi({
86
+ baseUrl: injectedContext?.plugin?.api_prefix ?? plugin.value?.api_prefix,
87
+ })
88
+
89
+ const resolvedName = computed(
90
+ () => pluginName ?? plugin.value?.name ?? injectedContext?.plugin?.name ?? '',
91
+ )
92
+ const usesPluginSettingsApi = computed(() => {
93
+ const currentPlugin = injectedContext?.plugin ?? plugin.value
94
+ return currentPlugin?.settings_api === 'plugin'
95
+ && resolvedName.value === currentPlugin.name
96
+ })
97
+
98
+ const config = ref<Record<string, unknown>>({})
99
+ const savedConfig = ref<Record<string, unknown>>({})
100
+ const revision = ref<string | null>(null)
101
+ const request = useRequestSyncState('Plugin config request failed.')
102
+ const isLoading = ref(false)
103
+ const isSaving = ref(false)
104
+ const error = request.error
105
+ const lastLoadedAt = request.lastLoadedAt
106
+ const lastSavedAt = request.lastSavedAt
107
+
108
+ const isDirty = computed(() => {
109
+ return JSON.stringify(config.value) !== JSON.stringify(savedConfig.value)
110
+ })
111
+
112
+ async function load(): Promise<void> {
113
+ const name = resolvedName.value
114
+ if (!name) return
115
+
116
+ isLoading.value = true
117
+ try {
118
+ const response = usesPluginSettingsApi.value
119
+ ? await request.run(
120
+ () => pluginApi.get<{
121
+ settings: Record<string, unknown>
122
+ mode?: string
123
+ revision?: string
124
+ }>('/settings'),
125
+ { success: 'load', errorMessage: 'Failed to load plugin config' },
126
+ )
127
+ : await request.run(
128
+ () => platformApi.get<{
129
+ plugin_name: string
130
+ config: Record<string, unknown>
131
+ revision?: string
132
+ }>(
133
+ `/plugins/${encodeURIComponent(name)}/config`,
134
+ ),
135
+ { success: 'load', errorMessage: 'Failed to load plugin config' },
136
+ )
137
+ const responseConfig = responseConfigPayload(response)
138
+ config.value = cloneConfig(responseConfig)
139
+ savedConfig.value = cloneConfig(responseConfig)
140
+ revision.value = response.revision ?? null
141
+ } catch {
142
+ // Error state is handled by request.run().
143
+ } finally {
144
+ isLoading.value = false
145
+ }
146
+ }
147
+
148
+ async function save(): Promise<boolean> {
149
+ const name = resolvedName.value
150
+ if (!name) return false
151
+
152
+ isSaving.value = true
153
+ try {
154
+ const payload = cloneConfig(config.value)
155
+ let response: {
156
+ plugin_name?: string
157
+ config?: Record<string, unknown>
158
+ settings?: Record<string, unknown>
159
+ mode?: string
160
+ revision?: string
161
+ } | undefined
162
+ let nextRevision = revision.value
163
+
164
+ if (usesPluginSettingsApi.value) {
165
+ response = await request.run(
166
+ () => pluginApi.put<{
167
+ settings: Record<string, unknown>
168
+ mode?: string
169
+ revision?: string
170
+ }>(
171
+ '/settings',
172
+ payload,
173
+ revision.value
174
+ ? { headers: { 'If-Match': `"${revision.value}"` } }
175
+ : undefined,
176
+ ),
177
+ { success: 'save', errorMessage: 'Failed to save plugin config' },
178
+ )
179
+ nextRevision = response.revision ?? nextRevision
180
+ } else {
181
+ const dirtyKeys = changedTopLevelKeys(payload, savedConfig.value)
182
+ const platformOwnedChanged = dirtyKeys.has(PLATFORM_OWNED_SETTINGS_KEY)
183
+ const typedKeys = [...dirtyKeys].filter(
184
+ key => key !== PLATFORM_OWNED_SETTINGS_KEY,
185
+ )
186
+
187
+ if (typedKeys.length) {
188
+ response = await request.run(
189
+ () => platformApi.patch<{
190
+ plugin_name: string
191
+ config: Record<string, unknown>
192
+ revision?: string
193
+ }>(
194
+ `/plugins/${encodeURIComponent(name)}/config`,
195
+ { config: topLevelConfigPatch(payload, typedKeys) },
196
+ ),
197
+ { success: 'save', errorMessage: 'Failed to save plugin config' },
198
+ )
199
+ nextRevision = response.revision ?? nextRevision
200
+ }
201
+
202
+ if (platformOwnedChanged) {
203
+ response = await request.run(
204
+ () => platformApi.patch<{
205
+ plugin_name: string
206
+ config: Record<string, unknown>
207
+ revision?: string
208
+ }>(
209
+ `/plugins/${encodeURIComponent(name)}/config`,
210
+ { config: platformOwnedConfigPatch(payload) },
211
+ ),
212
+ { success: 'save', errorMessage: 'Failed to save plugin config' },
213
+ )
214
+ nextRevision = response.revision ?? nextRevision
215
+ }
216
+ }
217
+
218
+ if (!response) return true
219
+ const responseConfig = responseConfigPayload(response)
220
+ config.value = cloneConfig(responseConfig)
221
+ savedConfig.value = cloneConfig(responseConfig)
222
+ revision.value = response.revision ?? nextRevision
223
+ return true
224
+ } catch {
225
+ return false
226
+ } finally {
227
+ isSaving.value = false
228
+ }
229
+ }
230
+
231
+ function reset(): void {
232
+ config.value = cloneConfig(savedConfig.value)
233
+ request.clearError()
234
+ }
235
+
236
+ onMounted(() => {
237
+ load()
238
+ })
239
+
240
+ return {
241
+ config,
242
+ isLoading,
243
+ isSaving,
244
+ error,
245
+ lastLoadedAt,
246
+ lastSavedAt,
247
+ isDirty,
248
+ load,
249
+ save,
250
+ reset,
251
+ }
252
+ }
@@ -0,0 +1,208 @@
1
+ import { computed } from 'vue'
2
+ import type { ComputedRef } from 'vue'
3
+ import type { PageSelectorItem, PillNavItem } from '../types'
4
+ import type {
5
+ PluginWorkspaceViewEmit,
6
+ PluginWorkspaceViewProps,
7
+ } from '../components/PluginWorkspaceView.props'
8
+ import { usePluginWorkspaceControls } from '../components/PluginWorkspaceView.controls'
9
+ import { usePluginWorkspaceNavigation } from '../components/PluginWorkspaceView.navigation'
10
+ import {
11
+ getPluginWorkspaceSidebarSlotNames,
12
+ getPluginWorkspaceTopBarSlotNames,
13
+ hasPluginWorkspaceSidebarSurface,
14
+ } from '../components/PluginWorkspaceView.shell'
15
+ import type {
16
+ PluginWorkspaceControlsModel,
17
+ PluginWorkspaceShellModel,
18
+ PluginWorkspaceSidebarModel,
19
+ } from './pluginWorkspaceModel'
20
+
21
+ /**
22
+ * Loosely-typed view of a component's `useSlots()` result: only slot presence and names are
23
+ * read here, so this accepts any `defineSlots()` shape without fighting Vue's `Slot` render
24
+ * function type. Mirrors the `SlotKeyMap` helper type in PluginWorkspaceView.shell.ts.
25
+ */
26
+ export type PluginWorkspaceSlotsLike = Readonly<Record<string, unknown>>
27
+
28
+ export interface UsePluginWorkspaceOptions {
29
+ /** PluginWorkspaceView's own reactive props object (from `defineProps`). */
30
+ props: PluginWorkspaceViewProps
31
+ /** PluginWorkspaceView's own slots (from `useSlots()`), used to detect provided passthrough slots. */
32
+ slots: PluginWorkspaceSlotsLike
33
+ /** PluginWorkspaceView's own typed `emit` function (from `defineEmits<PluginWorkspaceViewEmits>()`). */
34
+ emit: PluginWorkspaceViewEmit
35
+ }
36
+
37
+ export interface UsePluginWorkspaceReturn {
38
+ /** Model consumed by WorkspaceShellInternal's `shell` prop. */
39
+ shell: ComputedRef<PluginWorkspaceShellModel>
40
+ /** Model consumed by WorkspaceSidebarInternal's `sidebar` prop. */
41
+ sidebar: ComputedRef<PluginWorkspaceSidebarModel>
42
+ /** Model consumed by WorkspaceControlsInternal's `controls` prop, and by the raw `default` slot. */
43
+ controls: ComputedRef<PluginWorkspaceControlsModel>
44
+ /** Whether AppLayout should render a sidebar surface at all. */
45
+ hasSidebarSurface: ComputedRef<boolean>
46
+ /** Applies an AppSidebar `update:model-value` payload to the shared control values and emits `update:modelValue`/`update:values`. */
47
+ handleControlValuesUpdate: (values: Record<string, unknown>) => void
48
+ /** Resolves an AppTopBar `page-selector-select` payload into the active view and emits `page-selector-select`. */
49
+ handlePageSelectorSelect: (page: PageSelectorItem) => void
50
+ /** Resolves an AppTopBar `pill-select` payload into the active view and emits `pill-select`. */
51
+ handlePillSelect: (item: PillNavItem) => void
52
+ }
53
+
54
+ /**
55
+ * Maps PluginWorkspaceView's flat 72-prop surface into the three typed model objects
56
+ * (`shell`, `sidebar`, `controls`) rendered by its internal pieces
57
+ * (WorkspaceShellInternal, WorkspaceSidebarInternal, WorkspaceControlsInternal). Reuses the
58
+ * existing `usePluginWorkspaceControls` / `usePluginWorkspaceNavigation` composables and the
59
+ * PluginWorkspaceView.shell slot-detection helpers, so it changes only how that state is
60
+ * assembled and exposed, not the underlying behavior.
61
+ *
62
+ * @example Recommended
63
+ * ```ts
64
+ * const props = withDefaults(defineProps<PluginWorkspaceViewProps>(), { ... })
65
+ * const emit = defineEmits<PluginWorkspaceViewEmits>()
66
+ * const slots = useSlots()
67
+ * const { shell, sidebar, controls } = usePluginWorkspace({ props, slots, emit })
68
+ * ```
69
+ */
70
+ export function usePluginWorkspace(options: UsePluginWorkspaceOptions): UsePluginWorkspaceReturn {
71
+ const { props, slots, emit } = options
72
+
73
+ const controlsState = usePluginWorkspaceControls({
74
+ model: () => props.model,
75
+ controls: () => props.controls,
76
+ controlOptions: () => props.controlOptions ?? {},
77
+ modelValue: () => props.modelValue,
78
+ values: () => props.values,
79
+ componentBindings: () => props.componentBindings,
80
+ componentProps: () => props.componentProps,
81
+ componentPropsById: () => props.componentPropsById,
82
+ emitValues: (values) => {
83
+ emit('update:modelValue', values)
84
+ emit('update:values', values)
85
+ },
86
+ })
87
+
88
+ const navigationState = usePluginWorkspaceNavigation({
89
+ activeView: () => props.activeView,
90
+ defaultActiveView: () => props.defaultActiveView,
91
+ currentPillId: () => props.currentPillId,
92
+ currentPageSelectorId: () => props.currentPageSelectorId,
93
+ pillNav: () => props.pillNav,
94
+ pageSelector: () => props.pageSelector,
95
+ panelIds: () => Object.keys(props.panels ?? {}),
96
+ generatedPillNav: controlsState.generatedPillNav,
97
+ generatedDefaultView: controlsState.generatedDefaultView,
98
+ emitActiveView: viewId => emit('update:activeView', viewId),
99
+ emitPageSelectorSelect: page => emit('page-selector-select', page),
100
+ emitPillSelect: item => emit('pill-select', item),
101
+ })
102
+
103
+ const topBarSlotNames = computed<string[]>(() => getPluginWorkspaceTopBarSlotNames(slots))
104
+ const sidebarSlotNames = computed<string[]>(() => getPluginWorkspaceSidebarSlotNames(slots))
105
+
106
+ const hasSidebarSurface = computed(() => hasPluginWorkspaceSidebarSurface({
107
+ showSidebar: props.showSidebar ?? true,
108
+ panels: props.panels ?? {},
109
+ forms: props.forms ?? {},
110
+ model: props.model,
111
+ controls: props.controls,
112
+ showSidebarWhenEmpty: props.showSidebarWhenEmpty ?? false,
113
+ sidebarContentId: props.sidebarContentId,
114
+ sidebarTitle: props.sidebarTitle,
115
+ sidebarSubtitle: props.sidebarSubtitle,
116
+ sidebarBadge: props.sidebarBadge,
117
+ hasSidebarSlot: Boolean(slots.sidebar),
118
+ sidebarSlotCount: sidebarSlotNames.value.length,
119
+ }))
120
+
121
+ const controls = computed<PluginWorkspaceControlsModel>(() => ({
122
+ activeView: navigationState.resolvedActiveView.value,
123
+ setActiveView: navigationState.setActiveView,
124
+ values: controlsState.internalControlValues,
125
+ componentBindings: controlsState.resolvedComponentBindings.value,
126
+ componentBindingsById: controlsState.resolvedComponentBindingsById.value,
127
+ componentProps: controlsState.resolvedComponentProps.value,
128
+ componentPropsById: controlsState.resolvedComponentPropsById.value,
129
+ }))
130
+
131
+ const shell = computed<PluginWorkspaceShellModel>(() => ({
132
+ sidebarPosition: props.sidebarPosition ?? 'left',
133
+ sidebarWidth: props.sidebarWidth,
134
+ floating: props.floating ?? true,
135
+ responsiveSidebar: props.responsiveSidebar ?? true,
136
+ sidebarToggleLabel: props.sidebarToggleLabel ?? 'Open workspace sidebar',
137
+ sidebarCloseLabel: props.sidebarCloseLabel ?? 'Close workspace sidebar',
138
+ hasSidebarSurface: hasSidebarSurface.value,
139
+ title: props.title ?? 'Workspace',
140
+ subtitle: props.subtitle,
141
+ topBarVariant: props.topBarVariant ?? 'card',
142
+ homePath: props.homePath ?? '/',
143
+ showLogo: props.showLogo ?? true,
144
+ pageSelector: props.pageSelector,
145
+ currentPageSelectorId: navigationState.resolvedCurrentPageSelectorId.value,
146
+ pluginSwitcher: props.pluginSwitcher,
147
+ pillNav: navigationState.resolvedPillNav.value,
148
+ currentPillId: navigationState.resolvedCurrentPillId.value,
149
+ showThemeToggle: props.showThemeToggle ?? true,
150
+ showSettings: props.showSettings ?? true,
151
+ settingsConfig: props.settingsConfig,
152
+ showStandaloneLabel: props.showStandaloneLabel ?? false,
153
+ standaloneLabel: props.standaloneLabel ?? 'Standalone',
154
+ accountMenu: props.accountMenu,
155
+ showNotifications: props.showNotifications ?? false,
156
+ hasNotificationDot: props.hasNotificationDot ?? false,
157
+ showAdmin: props.showAdmin ?? false,
158
+ adminPath: props.adminPath ?? '/admin',
159
+ userName: props.userName,
160
+ userInitial: props.userInitial,
161
+ userEmail: props.userEmail,
162
+ topBarSlotNames: topBarSlotNames.value,
163
+ controls: controls.value,
164
+ }))
165
+
166
+ const sidebar = computed<PluginWorkspaceSidebarModel>(() => ({
167
+ title: props.sidebarTitle,
168
+ subtitle: props.sidebarSubtitle,
169
+ badge: props.sidebarBadge,
170
+ variant: props.sidebarVariant ?? 'analysis',
171
+ panels: props.panels ?? {},
172
+ activeView: navigationState.resolvedActiveView.value,
173
+ floating: false,
174
+ dense: props.dense ?? true,
175
+ width: props.sidebarWidth,
176
+ side: props.sidebarPosition ?? 'left',
177
+ forms: props.forms ?? {},
178
+ contentId: props.sidebarContentId,
179
+ showWhenEmpty: (props.showSidebarWhenEmpty ?? false) || props.sidebarContentId !== undefined,
180
+ model: props.model,
181
+ controlSchema: props.controls,
182
+ controlOptions: props.controlOptions ?? {},
183
+ values: controlsState.internalControlValues,
184
+ formEnhancements: props.formEnhancements,
185
+ showFormActions: props.showFormActions ?? false,
186
+ formLoading: props.formLoading ?? false,
187
+ formDisabled: props.formDisabled ?? false,
188
+ formReadonly: props.formReadonly ?? false,
189
+ formSize: props.formSize ?? 'sm',
190
+ collapsible: props.sidebarCollapsible,
191
+ collapsed: props.sidebarCollapsed,
192
+ defaultCollapsed: props.defaultSidebarCollapsed ?? false,
193
+ collapsedWidth: props.sidebarCollapsedWidth ?? '3rem',
194
+ collapseButtonLabel: props.sidebarCollapseButtonLabel ?? 'Collapse sidebar',
195
+ expandButtonLabel: props.sidebarExpandButtonLabel ?? 'Expand sidebar',
196
+ slotNames: sidebarSlotNames.value,
197
+ }))
198
+
199
+ return {
200
+ shell,
201
+ sidebar,
202
+ controls,
203
+ hasSidebarSurface,
204
+ handleControlValuesUpdate: controlsState.handleControlValuesUpdate,
205
+ handlePageSelectorSelect: navigationState.handlePageSelectorSelect,
206
+ handlePillSelect: navigationState.handlePillSelect,
207
+ }
208
+ }
@@ -1,5 +1,6 @@
1
1
  import { ref, computed, type Ref, type ComputedRef } from 'vue'
2
2
  import type { Rack, SlotPosition, WellPlateFormat, Well } from '../types'
3
+ import { PLATE_CONFIGS, rowLabel as plateRowLabel } from '../components/WellPlate.geometry'
3
4
 
4
5
  export interface UseRackEditorOptions {
5
6
  defaultFormat?: WellPlateFormat
@@ -31,15 +32,6 @@ export interface UseRackEditorReturn {
31
32
 
32
33
  const SLOT_CYCLE: SlotPosition[] = ['R', 'G', 'B', 'Y']
33
34
 
34
- const PLATE_CONFIGS: Record<WellPlateFormat, { rows: number; cols: number }> = {
35
- 6: { rows: 2, cols: 3 },
36
- 12: { rows: 3, cols: 4 },
37
- 24: { rows: 4, cols: 6 },
38
- 48: { rows: 6, cols: 8 },
39
- 54: { rows: 6, cols: 9 },
40
- 96: { rows: 8, cols: 12 },
41
- 384: { rows: 16, cols: 24 },
42
- }
43
35
 
44
36
  let globalIdCounter = 0
45
37
 
@@ -156,8 +148,7 @@ export function useRackEditor(
156
148
  let counter = 1
157
149
  for (let row = 0; row < config.rows; row++) {
158
150
  for (let col = 0; col < config.cols; col++) {
159
- const rowLabel = String.fromCharCode(65 + row)
160
- const wellId = `${rowLabel}${col + 1}`
151
+ const wellId = `${plateRowLabel(row)}${col + 1}`
161
152
  // Only fill empty wells
162
153
  if (!rack.wells[wellId]?.sampleType) {
163
154
  const paddedNum = String(counter).padStart(3, '0')