@morscherlab/mint-sdk 1.4.0-beta.2 → 1.4.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 (330) hide show
  1. package/README.md +14 -3
  2. package/dist/{ActionMenu-CScINahU.js → AlertBox-CzLg0UaL.js} +40 -209
  3. package/dist/AlertBox-CzLg0UaL.js.map +1 -0
  4. package/dist/{AppSidebar-BmiiPfz9.js → AppSidebar-6oTT-gPO.js} +1249 -239
  5. package/dist/AppSidebar-6oTT-gPO.js.map +1 -0
  6. package/dist/{ConfirmDialog-CJ7hqYUP.js → ConfirmDialog-CSls2Nuv.js} +2 -2
  7. package/dist/{ConfirmDialog-CJ7hqYUP.js.map → ConfirmDialog-CSls2Nuv.js.map} +1 -1
  8. package/dist/ExperimentPopover-9l4QZlvQ.js +337 -0
  9. package/dist/ExperimentPopover-9l4QZlvQ.js.map +1 -0
  10. package/dist/{ExperimentSelectorModal-D-9z7cNx.js → ExperimentSelectorModal-pR5fK3pA.js} +244 -211
  11. package/dist/ExperimentSelectorModal-pR5fK3pA.js.map +1 -0
  12. package/dist/{SegmentedControl-DERBN-Xd.js → SegmentedControl-CJ7EAmOq.js} +2 -2
  13. package/dist/{SegmentedControl-DERBN-Xd.js.map → SegmentedControl-CJ7EAmOq.js.map} +1 -1
  14. package/dist/{SettingsModal-CSLE31yg.js → SettingsModal-Dyitgzfp.js} +28 -27
  15. package/dist/SettingsModal-Dyitgzfp.js.map +1 -0
  16. package/dist/__stories__/experiment-helpers.d.ts +10 -0
  17. package/dist/__tests__/components/AppTopBarOnboarding.test.d.ts +1 -0
  18. package/dist/__tests__/composables/useOnboarding.test.d.ts +1 -0
  19. package/dist/__tests__/styles/paletteTokens.test.d.ts +1 -0
  20. package/dist/__tests__/styles/v2AuditRules.test.d.ts +1 -0
  21. package/dist/components/AppSidebar.vue.d.ts +1 -1
  22. package/dist/components/AppTopBar.vue.d.ts +20 -5
  23. package/dist/components/ArtifactSaveDialog.vue.d.ts +9 -1
  24. package/dist/components/AuditTrail.vue.d.ts +1 -1
  25. package/dist/components/BaseButton.vue.d.ts +4 -2
  26. package/dist/components/BaseSelect.vue.d.ts +2 -2
  27. package/dist/components/ChartContainer.vue.d.ts +2 -0
  28. package/dist/components/DatePicker.vue.d.ts +1 -1
  29. package/dist/components/ExperimentPopover.vue.d.ts +18 -0
  30. package/dist/components/ExperimentSelectorModal.vue.d.ts +15 -5
  31. package/dist/components/FilePicker.vue.d.ts +1 -1
  32. package/dist/components/FileUploader.vue.d.ts +2 -2
  33. package/dist/components/JobsStatusTray.vue.d.ts +15 -4
  34. package/dist/components/PluginIcon.vue.d.ts +2 -2
  35. package/dist/components/PluginWorkspaceView.props.d.ts +3 -0
  36. package/dist/components/PluginWorkspaceView.vue.d.ts +3 -0
  37. package/dist/components/SampleSelector.vue.d.ts +6 -6
  38. package/dist/components/SequenceProgressBar.vue.d.ts +3 -0
  39. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +1 -1
  40. package/dist/components/StepWizard.vue.d.ts +3 -0
  41. package/dist/components/internal/AppTopBarOnboardingInternal.vue.d.ts +7 -0
  42. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -1
  43. package/dist/components/internal/AutoGroupModalInternal.vue.d.ts +1 -1
  44. package/dist/components/internal/DatePickerTimeInternal.vue.d.ts +1 -1
  45. package/dist/components/internal/OnboardingLayerInternal.vue.d.ts +7 -0
  46. package/dist/components/internal/ResourcePickerListInternal.vue.d.ts +2 -7
  47. package/dist/components/internal/SelectMultipleInternal.vue.d.ts +4 -0
  48. package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +8 -0
  49. package/dist/composables/createClient.d.ts +2 -0
  50. package/dist/composables/index.d.ts +1 -0
  51. package/dist/composables/pluginIdentity.d.ts +24 -0
  52. package/dist/composables/pluginWorkspaceModel.d.ts +2 -2
  53. package/dist/composables/useAppExperiment.d.ts +8 -0
  54. package/dist/composables/useCalendarGrid.d.ts +1 -1
  55. package/dist/composables/useExperimentSelector.d.ts +6 -2
  56. package/dist/composables/useJobCenter.d.ts +41 -3
  57. package/dist/composables/useOnboarding.d.ts +119 -0
  58. package/dist/composables/usePlatform.d.ts +1 -0
  59. package/dist/{controlSchemaModel-CP5-_nAj.js → controlSchemaModel-DCUOx8ll.js} +2 -2
  60. package/dist/{controlSchemaModel-CP5-_nAj.js.map → controlSchemaModel-DCUOx8ll.js.map} +1 -1
  61. package/dist/{data-C2PwI_dn.js → data-DBnmeBZX.js} +179 -125
  62. package/dist/data-DBnmeBZX.js.map +1 -0
  63. package/dist/data.js +8 -8
  64. package/dist/{experiment-43OJtH7R.js → experiment-DyO54FQB.js} +25 -61
  65. package/dist/experiment-DyO54FQB.js.map +1 -0
  66. package/dist/{experimentStatus-BZmU4czV.js → experimentStatus-BFrKpHAH.js} +3 -3
  67. package/dist/{experimentStatus-BZmU4czV.js.map → experimentStatus-BFrKpHAH.js.map} +1 -1
  68. package/dist/index.js +20 -20
  69. package/dist/{install-DAID6c2-.js → install-qjELyNwc.js} +15 -14
  70. package/dist/{install-DAID6c2-.js.map → install-qjELyNwc.js.map} +1 -1
  71. package/dist/install.js +1 -1
  72. package/dist/{lab-DLwRl_Ql.js → lab-UyTbokQC.js} +136 -148
  73. package/dist/lab-UyTbokQC.js.map +1 -0
  74. package/dist/lab.js +4 -3
  75. package/dist/{records-BzTcxekG.js → records-CR_pYzIP.js} +533 -335
  76. package/dist/records-CR_pYzIP.js.map +1 -0
  77. package/dist/{runtime-D6gVat6r.js → runtime-CoHhBP10.js} +417 -54
  78. package/dist/runtime-CoHhBP10.js.map +1 -0
  79. package/dist/runtime.d.ts +1 -1
  80. package/dist/runtime.js +7 -7
  81. package/dist/{shell-5ju97nOS.js → shell-BDuZf2zb.js} +10 -5
  82. package/dist/{shell-5ju97nOS.js.map → shell-BDuZf2zb.js.map} +1 -1
  83. package/dist/shell.d.ts +1 -0
  84. package/dist/shell.js +6 -6
  85. package/dist/stores/index.d.ts +1 -1
  86. package/dist/stores/index.js +2 -2
  87. package/dist/stores/settings.d.ts +4 -12
  88. package/dist/styles.css +2732 -2083
  89. package/dist/types/componentWorkflowTypes.d.ts +2 -0
  90. package/dist/types/components.d.ts +2 -0
  91. package/dist/types/generated/platformContext.d.ts +1 -0
  92. package/dist/types/generated-ui.d.ts +4 -0
  93. package/dist/types/index.d.ts +1 -1
  94. package/dist/types/jobs.d.ts +9 -0
  95. package/dist/types/platform.d.ts +1 -1
  96. package/dist/{BaseModal-DfRIuVoj.js → useDropdownState-Bb2N4m40.js} +74 -4
  97. package/dist/useDropdownState-Bb2N4m40.js.map +1 -0
  98. package/dist/{useExperimentSamples-CxcnxZ8h.js → useExperimentSamples-Dno1fFMq.js} +4 -4
  99. package/dist/{useExperimentSamples-CxcnxZ8h.js.map → useExperimentSamples-Dno1fFMq.js.map} +1 -1
  100. package/dist/{useListReorder-CcbEYxX-.js → useListReorder-CQ9OCScc.js} +183 -351
  101. package/dist/useListReorder-CQ9OCScc.js.map +1 -0
  102. package/dist/{useRackEditor-BD5NIo5s.js → useRackEditor-wL3kPrRT.js} +73 -59
  103. package/dist/useRackEditor-wL3kPrRT.js.map +1 -0
  104. package/dist/useRealtime-C3whhVvb.js +508 -0
  105. package/dist/useRealtime-C3whhVvb.js.map +1 -0
  106. package/dist/useRequest-CDkrXNU7.js +244 -0
  107. package/dist/useRequest-CDkrXNU7.js.map +1 -0
  108. package/dist/utils/time.d.ts +7 -0
  109. package/package.json +1 -1
  110. package/src/__stories__/experiment-helpers.ts +3 -3
  111. package/src/__tests__/components/AppTopBar.test.ts +257 -3
  112. package/src/__tests__/components/AppTopBarOnboarding.test.ts +160 -0
  113. package/src/__tests__/components/AppTopBarPageSelector.test.ts +125 -9
  114. package/src/__tests__/components/AppTopBarPillNav.test.ts +35 -0
  115. package/src/__tests__/components/ArtifactSaveDialog.test.ts +78 -0
  116. package/src/__tests__/components/BaseButton.test.ts +19 -0
  117. package/src/__tests__/components/BaseSelect.multiple.test.ts +87 -32
  118. package/src/__tests__/components/BaseSelect.test.ts +2 -1
  119. package/src/__tests__/components/BatchProgressList.test.ts +16 -0
  120. package/src/__tests__/components/CalendarGridPanel.test.ts +2 -2
  121. package/src/__tests__/components/ChartContainer.test.ts +11 -0
  122. package/src/__tests__/components/ChemicalFormula.test.ts +6 -3
  123. package/src/__tests__/components/DatePicker.datetime.test.ts +6 -0
  124. package/src/__tests__/components/DatePicker.test.ts +21 -0
  125. package/src/__tests__/components/DatePicker.time.test.ts +2 -2
  126. package/src/__tests__/components/EmptyState.test.ts +1 -1
  127. package/src/__tests__/components/ExperimentPopover.test.ts +125 -16
  128. package/src/__tests__/components/ExperimentSelectorModal.test.ts +153 -14
  129. package/src/__tests__/components/GeneratedPluginView.test.ts +23 -2
  130. package/src/__tests__/components/InstrumentStateBadge.test.ts +4 -4
  131. package/src/__tests__/components/JobsStatusTray.test.ts +373 -71
  132. package/src/__tests__/components/PluginWorkspaceView.test.ts +63 -1
  133. package/src/__tests__/components/ResourcePickerListInternal.test.ts +0 -20
  134. package/src/__tests__/components/SampleSelector.test.ts +33 -8
  135. package/src/__tests__/components/SequenceProgressBar.test.ts +12 -0
  136. package/src/__tests__/components/SettingsModal.test.ts +9 -2
  137. package/src/__tests__/components/StepWizard.test.ts +11 -0
  138. package/src/__tests__/components/WellPlate.decoration.test.ts +11 -4
  139. package/src/__tests__/components/deprecationTags.test.ts +1 -1
  140. package/src/__tests__/composables/useAppExperiment.test.ts +27 -1
  141. package/src/__tests__/composables/useCalendarGrid.test.ts +4 -2
  142. package/src/__tests__/composables/useExperimentSelector.test.ts +111 -0
  143. package/src/__tests__/composables/useJobCenter.test.ts +262 -3
  144. package/src/__tests__/composables/useOnboarding.test.ts +317 -0
  145. package/src/__tests__/docs/frontendDocsCatalog.test.ts +4 -3
  146. package/src/__tests__/stores/settings.test.ts +21 -21
  147. package/src/__tests__/styles/baseControlsRules.test.ts +6 -2
  148. package/src/__tests__/styles/controlRefreshRules.test.ts +0 -6
  149. package/src/__tests__/styles/cssTokens.test.ts +6 -3
  150. package/src/__tests__/styles/menusShellRefresh.test.ts +22 -12
  151. package/src/__tests__/styles/paletteTokens.test.ts +44 -0
  152. package/src/__tests__/styles/v2AuditRules.test.ts +104 -0
  153. package/src/__tests__/utils/plotlyTemplate.test.ts +4 -4
  154. package/src/__tests__/utils/time.test.ts +18 -1
  155. package/src/components/AdductText.story.vue +7 -0
  156. package/src/components/AdductText.vue +1 -1
  157. package/src/components/AlertBox.story.vue +2 -2
  158. package/src/components/AlertBox.vue +18 -16
  159. package/src/components/AppSidebar.story.vue +0 -28
  160. package/src/components/AppTopBar.onboarding.story.vue +69 -0
  161. package/src/components/AppTopBar.story.vue +105 -15
  162. package/src/components/AppTopBar.vue +248 -122
  163. package/src/components/ArtifactSaveDialog.story.vue +23 -0
  164. package/src/components/ArtifactSaveDialog.vue +102 -12
  165. package/src/components/AuditTrail.story.vue +2 -1
  166. package/src/components/AuditTrail.vue +2 -21
  167. package/src/components/Avatar.vue +10 -14
  168. package/src/components/BaseButton.vue +16 -2
  169. package/src/components/BaseSelect.multiple.story.vue +13 -2
  170. package/src/components/BaseSelect.vue +4 -3
  171. package/src/components/BatchProgressList.vue +35 -25
  172. package/src/components/ChartContainer.story.vue +2 -1
  173. package/src/components/ChartContainer.vue +8 -6
  174. package/src/components/ConfirmDialog.story.vue +1 -1
  175. package/src/components/EmptyState.vue +1 -1
  176. package/src/components/ExperimentPopover.story.vue +165 -53
  177. package/src/components/ExperimentPopover.vue +169 -187
  178. package/src/components/ExperimentSelectorModal.story.vue +73 -31
  179. package/src/components/ExperimentSelectorModal.vue +144 -111
  180. package/src/components/FormField.story.vue +8 -48
  181. package/src/components/GeneratedPluginView.vue +11 -10
  182. package/src/components/GeneratedResultRenderer.vue +3 -4
  183. package/src/components/InstrumentStateBadge.vue +1 -1
  184. package/src/components/JobsStatusTray.story.vue +97 -43
  185. package/src/components/JobsStatusTray.vue +287 -290
  186. package/src/components/LayoutResizeHandle.story.vue +65 -4
  187. package/src/components/LoadingSpinner.vue +1 -1
  188. package/src/components/NumberInput.vue +3 -6
  189. package/src/components/PluginIcon.story.vue +2 -10
  190. package/src/components/PluginIcon.vue +2 -2
  191. package/src/components/PluginWorkspaceView.props.ts +3 -0
  192. package/src/components/PluginWorkspaceView.story.vue +1 -1
  193. package/src/components/PluginWorkspaceView.vue +17 -0
  194. package/src/components/RuntimeUpdateNotice.vue +1 -1
  195. package/src/components/SampleSelector.story.vue +22 -7
  196. package/src/components/SampleSelector.vue +34 -37
  197. package/src/components/SampleSelectorGroupNode.vue +1 -1
  198. package/src/components/SectionCard.story.vue +35 -2
  199. package/src/components/SequenceProgressBar.story.vue +9 -0
  200. package/src/components/SequenceProgressBar.vue +6 -1
  201. package/src/components/SettingsModal.schema.ts +1 -1
  202. package/src/components/SettingsModal.vue +9 -14
  203. package/src/components/SmartGroupFieldRecipe.story.vue +1 -1
  204. package/src/components/SmartGroupManual.story.vue +1 -1
  205. package/src/components/StepWizard.story.vue +33 -0
  206. package/src/components/StepWizard.vue +8 -1
  207. package/src/components/WellPlate.story.vue +3 -1
  208. package/src/components/WellPlate.vue +7 -0
  209. package/src/components/internal/AppTopBarOnboardingInternal.vue +172 -0
  210. package/src/components/internal/AppTopBarPageSelectorInternal.vue +140 -72
  211. package/src/components/internal/AppTopBarPillNavInternal.vue +33 -2
  212. package/src/components/internal/DatePickerDateInternal.vue +16 -40
  213. package/src/components/internal/DatePickerDateTimeInternal.vue +3 -13
  214. package/src/components/internal/DatePickerTimeInternal.vue +5 -8
  215. package/src/components/internal/OnboardingLayerInternal.vue +173 -0
  216. package/src/components/internal/ResourcePickerListInternal.vue +45 -85
  217. package/src/components/internal/SelectMultipleInternal.vue +182 -65
  218. package/src/components/internal/SelectSearchableInternal.vue +2 -3
  219. package/src/components/internal/WorkspaceShellInternal.vue +12 -0
  220. package/src/composables/createClient.ts +2 -0
  221. package/src/composables/index.ts +1 -0
  222. package/src/composables/pluginIdentity.ts +31 -0
  223. package/src/composables/pluginWorkspaceModel.ts +2 -2
  224. package/src/composables/useAppExperiment.ts +20 -0
  225. package/src/composables/useCalendarGrid.ts +4 -3
  226. package/src/composables/useExperimentSelector.ts +77 -31
  227. package/src/composables/useJobCenter.ts +188 -6
  228. package/src/composables/useOnboarding.ts +465 -0
  229. package/src/composables/usePluginWorkspace.ts +1 -1
  230. package/src/runtime.ts +2 -1
  231. package/src/shell.ts +17 -0
  232. package/src/stores/index.ts +0 -2
  233. package/src/stores/settings.ts +20 -50
  234. package/src/styles/components/adduct-text.css +13 -5
  235. package/src/styles/components/alert-box.css +39 -58
  236. package/src/styles/components/app-layout.css +7 -5
  237. package/src/styles/components/app-page-selector.css +121 -44
  238. package/src/styles/components/app-pill-nav.css +11 -12
  239. package/src/styles/components/app-sidebar.css +9 -18
  240. package/src/styles/components/app-top-bar.css +219 -55
  241. package/src/styles/components/artifact-save-dialog.css +153 -2
  242. package/src/styles/components/audit-trail.css +16 -19
  243. package/src/styles/components/avatar.css +6 -4
  244. package/src/styles/components/batch-progress-list.css +38 -36
  245. package/src/styles/components/button.css +36 -59
  246. package/src/styles/components/calendar.css +12 -12
  247. package/src/styles/components/chart-container.css +34 -23
  248. package/src/styles/components/checkbox.css +2 -12
  249. package/src/styles/components/collapsible-card.css +27 -29
  250. package/src/styles/components/confirm-dialog.css +13 -14
  251. package/src/styles/components/dataframe.css +13 -5
  252. package/src/styles/components/date-picker.css +80 -92
  253. package/src/styles/components/datetime-picker.css +31 -43
  254. package/src/styles/components/divider.css +20 -20
  255. package/src/styles/components/dose-calculator.css +3 -3
  256. package/src/styles/components/empty-state.css +20 -17
  257. package/src/styles/components/experiment-code-badge.css +2 -0
  258. package/src/styles/components/experiment-popover.css +174 -329
  259. package/src/styles/components/experiment-selector-modal.css +88 -56
  260. package/src/styles/components/file-picker.css +3 -3
  261. package/src/styles/components/file-uploader.css +38 -49
  262. package/src/styles/components/form-builder.css +3 -3
  263. package/src/styles/components/formula-input.css +1 -1
  264. package/src/styles/components/icon-button.css +18 -24
  265. package/src/styles/components/input.css +5 -4
  266. package/src/styles/components/instrument-monitor.css +13 -15
  267. package/src/styles/components/jobs-status-tray.css +140 -296
  268. package/src/styles/components/layout-resize-handle.css +59 -16
  269. package/src/styles/components/modal.css +4 -6
  270. package/src/styles/components/molecule-input.css +2 -2
  271. package/src/styles/components/multi-select.css +65 -117
  272. package/src/styles/components/number-input.css +26 -19
  273. package/src/styles/components/onboarding.css +368 -0
  274. package/src/styles/components/pill.css +3 -2
  275. package/src/styles/components/plate-editor.css +0 -1
  276. package/src/styles/components/plugin-icon.css +26 -23
  277. package/src/styles/components/popover.css +2 -2
  278. package/src/styles/components/presence-avatars.css +1 -1
  279. package/src/styles/components/progress-bar.css +3 -3
  280. package/src/styles/components/protocol-step-editor.css +1 -3
  281. package/src/styles/components/resource-card.css +2 -2
  282. package/src/styles/components/resource-picker-list.css +6 -57
  283. package/src/styles/components/sample-hierarchy-tree.css +4 -4
  284. package/src/styles/components/sample-selector.css +138 -79
  285. package/src/styles/components/schedule-calendar.css +8 -8
  286. package/src/styles/components/searchable-select.css +12 -12
  287. package/src/styles/components/section-card.css +54 -49
  288. package/src/styles/components/segmented-control.css +18 -15
  289. package/src/styles/components/select.css +3 -3
  290. package/src/styles/components/sequence-input.css +2 -2
  291. package/src/styles/components/sequence-progress-bar.css +17 -8
  292. package/src/styles/components/settings-modal.css +7 -2
  293. package/src/styles/components/smart-group.css +18 -18
  294. package/src/styles/components/step-wizard.css +52 -16
  295. package/src/styles/components/tabs.css +2 -2
  296. package/src/styles/components/textarea.css +3 -3
  297. package/src/styles/components/time-picker.css +36 -21
  298. package/src/styles/components/toast.css +12 -10
  299. package/src/styles/components/toggle.css +1 -11
  300. package/src/styles/components/tooltip.css +10 -9
  301. package/src/styles/components/unit-input.css +8 -7
  302. package/src/styles/components/well-plate.css +9 -2
  303. package/src/styles/index.css +1 -0
  304. package/src/styles/variables.css +241 -78
  305. package/src/types/componentWorkflowTypes.ts +2 -0
  306. package/src/types/components.ts +2 -0
  307. package/src/types/generated/platformContext.ts +1 -0
  308. package/src/types/generated-ui.ts +4 -0
  309. package/src/types/index.ts +1 -0
  310. package/src/types/jobs.ts +10 -0
  311. package/src/types/platform.ts +1 -1
  312. package/src/utils/time.ts +21 -0
  313. package/dist/ActionMenu-CScINahU.js.map +0 -1
  314. package/dist/AppSidebar-BmiiPfz9.js.map +0 -1
  315. package/dist/BaseModal-DfRIuVoj.js.map +0 -1
  316. package/dist/ExperimentPopover-C4sGw-is.js +0 -359
  317. package/dist/ExperimentPopover-C4sGw-is.js.map +0 -1
  318. package/dist/ExperimentSelectorModal-D-9z7cNx.js.map +0 -1
  319. package/dist/SettingsModal-CSLE31yg.js.map +0 -1
  320. package/dist/data-C2PwI_dn.js.map +0 -1
  321. package/dist/experiment-43OJtH7R.js.map +0 -1
  322. package/dist/lab-DLwRl_Ql.js.map +0 -1
  323. package/dist/records-BzTcxekG.js.map +0 -1
  324. package/dist/runtime-D6gVat6r.js.map +0 -1
  325. package/dist/useListReorder-CcbEYxX-.js.map +0 -1
  326. package/dist/useRackEditor-BD5NIo5s.js.map +0 -1
  327. package/dist/useRealtime-PalKue84.js +0 -696
  328. package/dist/useRealtime-PalKue84.js.map +0 -1
  329. package/dist/useRequest-D5JDfsgZ.js +0 -76
  330. package/dist/useRequest-D5JDfsgZ.js.map +0 -1
@@ -107,17 +107,6 @@ describe('ResourcePickerListInternal', () => {
107
107
  expect(wrapper.emitted('update:search')?.[0]).toEqual(['plasma'])
108
108
  })
109
109
 
110
- it('should head each group with its label and a count', () => {
111
- const wrapper = setup({ groups: [{ label: 'Recent', items }] })
112
- expect(wrapper.get('.mint-resource-picker__group').text()).toBe('Recent2')
113
- })
114
-
115
- it('should hide the rows of a collapsed group', async () => {
116
- const wrapper = setup({ groups: [{ label: 'Recent', items }] })
117
- await wrapper.get('.mint-resource-picker__group').trigger('click')
118
- expect(wrapper.findAll('[role="option"]')).toHaveLength(0)
119
- })
120
-
121
110
  it('should show skeleton rows while loading', () => {
122
111
  expect(setup({ loading: true }).findAll('.mint-resource-picker__skeleton-row')).toHaveLength(4)
123
112
  })
@@ -129,13 +118,4 @@ describe('ResourcePickerListInternal', () => {
129
118
  it('should show the empty title when there are no items', () => {
130
119
  expect(setup({ items: [], emptyTitle: 'Nothing here' }).text()).toContain('Nothing here')
131
120
  })
132
-
133
- it('should leave Enter to a group header while a row is highlighted', async () => {
134
- const wrapper = setup({ groups: [{ label: 'Recent', items }] })
135
- await wrapper.findAll('[role="option"]')[0]!.trigger('mouseenter')
136
-
137
- await wrapper.get('.mint-resource-picker__group').trigger('keydown', { key: 'Enter' })
138
-
139
- expect(wrapper.emitted('select')).toBeUndefined()
140
- })
141
121
  })
@@ -97,7 +97,7 @@ describe('SampleSelector', () => {
97
97
  },
98
98
  })
99
99
 
100
- expect(wrapper.find('.mint-sample-selector__select-all-count').text()).toBe('0 / 2')
100
+ expect(wrapper.find('.mint-sample-selector__select-all-count').text()).toBe('0/2')
101
101
  expect(wrapper.findAll('.mint-sample-selector__flat-name').map(item => item.text())).toEqual(['S1', 'S2'])
102
102
  })
103
103
 
@@ -115,9 +115,9 @@ describe('SampleSelector', () => {
115
115
  },
116
116
  })
117
117
 
118
- const labels = wrapper.findAll('.mint-sample-selector__actions-row .mint-sample-selector__action-label')
119
- expect(labels.map(label => label.text())).toEqual(['Grouping'])
120
- expect(wrapper.findAll('.mint-sample-selector__actions-row > .mint-sample-selector__action-btn')).toHaveLength(2)
118
+ const buttons = wrapper.findAll('.mint-sample-selector__header-actions > .mint-sample-selector__action-btn')
119
+ expect(buttons.map(button => button.text())).toEqual(['Smart group'])
120
+ expect(wrapper.find('.mint-sample-selector__header').text()).toContain('Samples0 selected')
121
121
 
122
122
  await wrapper.findAllComponents({ name: 'BaseButton' })[0].trigger('click')
123
123
 
@@ -125,6 +125,26 @@ describe('SampleSelector', () => {
125
125
  expect(modal.props('initialMode')).toBe('auto')
126
126
  })
127
127
 
128
+ it.each([
129
+ [true, 'Smart group'],
130
+ [false, 'Group'],
131
+ ])('should keep the grouping label as accessible name and tooltip when smart group is %s', (enableSmartGroup, label) => {
132
+ const wrapper = mount(SampleSelector, {
133
+ props: { samples: ['S1', 'S2'], modelValue: ['S1'], groups: [], enableSmartGroup },
134
+ global: { stubs: { AutoGroupModalInternal: true } },
135
+ })
136
+ const button = wrapper.get('.mint-sample-selector__header-actions > .mint-sample-selector__action-btn')
137
+ const count = wrapper.get('.mint-sample-selector__selected')
138
+
139
+ expect({
140
+ name: button.text(),
141
+ title: button.attributes('title'),
142
+ full: count.get('.mint-sample-selector__selected-full').text(),
143
+ short: count.get('.mint-sample-selector__selected-short').text(),
144
+ shortHidden: count.get('.mint-sample-selector__selected-short').attributes('aria-hidden'),
145
+ }).toEqual({ name: label, title: label, full: '1 selected', short: '1 sel.', shortHidden: 'true' })
146
+ })
147
+
128
148
  it('opens the grouping modal in manual mode', async () => {
129
149
  const wrapper = mount(SampleSelector, {
130
150
  props: {
@@ -140,8 +160,8 @@ describe('SampleSelector', () => {
140
160
  },
141
161
  })
142
162
 
143
- const labels = wrapper.findAll('.mint-sample-selector__actions-row .mint-sample-selector__action-label')
144
- expect(labels.map(label => label.text())).toEqual(['Grouping'])
163
+ const buttons = wrapper.findAll('.mint-sample-selector__header-actions > .mint-sample-selector__action-btn')
164
+ expect(buttons.map(button => button.text())).toEqual(['Group'])
145
165
 
146
166
  await wrapper.findAllComponents({ name: 'BaseButton' })[0].trigger('click')
147
167
 
@@ -472,9 +492,14 @@ describe('SampleSelector checkboxes (7c)', () => {
472
492
  }
473
493
  const stubs = { global: { stubs: { AutoGroupModalInternal: true } } }
474
494
 
475
- it('should show the mono selected / total count when some samples are selected', () => {
495
+ it('should show the mono selected/total count when some samples are selected', () => {
476
496
  const wrapper = mount(SampleSelector, { props: grouped, ...stubs })
477
- expect(wrapper.find('.mint-sample-selector__select-all-count').text()).toBe('1 / 3')
497
+ expect(wrapper.find('.mint-sample-selector__select-all-count').text()).toBe('1/3')
498
+ })
499
+
500
+ it('should offer clear-all-groups in the header when groups exist', () => {
501
+ const wrapper = mount(SampleSelector, { props: { ...grouped, enableGrouping: true }, ...stubs })
502
+ expect(wrapper.find('.mint-sample-selector__header-actions [aria-label="Clear all groups"]').exists()).toBe(true)
478
503
  })
479
504
 
480
505
  it('should mark the select-all checkbox mixed when some samples are selected', () => {
@@ -36,4 +36,16 @@ describe('SequenceProgressBar', () => {
36
36
  expect(wrapper.get('.mint-sequence-progress__label').text()).toBe('2 / 4 injections')
37
37
  expect(wrapper.find('.mint-sequence-progress__footer').exists()).toBe(false)
38
38
  })
39
+
40
+ it.each([
41
+ [0, null],
42
+ [2, '2 failed'],
43
+ ])('should show the failed count when %i injections failed', (failed, expected) => {
44
+ const wrapper = mount(SequenceProgressBar, {
45
+ props: { failed, progress: { current_sample: 3, total_samples: 6 } },
46
+ })
47
+
48
+ const node = wrapper.find('.mint-sequence-progress__failed')
49
+ expect(node.exists() ? node.text() : null).toBe(expected)
50
+ })
39
51
  })
@@ -728,10 +728,17 @@ describe('SettingsModal', () => {
728
728
  expect(useSettingsStore(pinia).theme).toBe('dark')
729
729
  })
730
730
 
731
- it('should render a swatch strip for each palette when appearance is shown', () => {
731
+ it('should preview each palette with its own tokens when appearance is shown', () => {
732
732
  const wrapper = mountSettingsModal({ showAppearance: true })
733
733
 
734
- expect(wrapper.findAll('.mint-settings-modal__palette')).toHaveLength(Object.keys(colorPalettes).length)
734
+ const strips = wrapper.findAll('.mint-settings-modal__swatches')
735
+ expect(strips.map((strip) => strip.attributes('data-palette'))).toEqual(Object.keys(colorPalettes))
736
+ expect(strips[0].findAll('.mint-settings-modal__swatch').map((swatch) => swatch.attributes('style'))).toEqual([
737
+ 'background-color: var(--color-primary);',
738
+ 'background-color: var(--color-primary-hover);',
739
+ 'background-color: var(--color-primary-soft);',
740
+ 'background-color: var(--color-primary-border);',
741
+ ])
735
742
  })
736
743
 
737
744
  it('should resize the density preview when the density changes', async () => {
@@ -73,3 +73,14 @@ describe('StepWizard navigation (2c)', () => {
73
73
  expect(wrapper.find('.mint-wizard__counter').text()).toBe('2 / 2')
74
74
  })
75
75
  })
76
+
77
+ describe('StepWizard orientation', () => {
78
+ it.each([
79
+ ['horizontal', false],
80
+ ['vertical', true],
81
+ ] as const)('should toggle the vertical modifier when orientation is %s', (orientation, vertical) => {
82
+ const wrapper = mount(StepWizard, { props: { steps, orientation } })
83
+
84
+ expect(wrapper.classes('mint-wizard--vertical')).toBe(vertical)
85
+ })
86
+ })
@@ -28,15 +28,22 @@ describe('WellPlate per-well decoration', () => {
28
28
  .toEqual(['2px', 'solid', 'var(--mint-warning)'])
29
29
  })
30
30
 
31
- it('should render no glyph when a QC well has no label or id', () => {
31
+ it.each([
32
+ ['qc', {}, 'Q'],
33
+ ['iqc', {}, 'i'],
34
+ ['sample', {}, ''],
35
+ ['qc', { showWellIds: true }, 'A1'],
36
+ ] as const)('should render the %s glyph when the well has no label (spec %o)', (sampleType, spec, text) => {
32
37
  const wrapper = mount(WellPlate, {
33
38
  props: {
34
- spec: { format: 6 },
35
- wells: { A1: { id: 'A1', row: 0, col: 0, state: 'filled', sampleType: 'qc' } },
39
+ spec: { format: 6, ...spec },
40
+ wells: { A1: { id: 'A1', row: 0, col: 0, state: 'filled', sampleType } },
36
41
  },
37
42
  })
38
43
 
39
- expect(wrapper.findAll('.mint-well-plate__well')[0].text()).toBe('')
44
+ const well = wrapper.findAll('.mint-well-plate__well')[0]
45
+ expect(well.text()).toBe(text)
46
+ expect(well.find('.mint-well-plate__indicator').exists()).toBe(text === 'Q' || text === 'i')
40
47
  })
41
48
 
42
49
  it('should draw plain legend swatches when the legend is shown', () => {
@@ -68,7 +68,7 @@ describe('root exports outside L1 and L2', () => {
68
68
 
69
69
  expect(values.sort()).toEqual([
70
70
  'defineControlModel', 'defineDataFrame', 'definePluginWorkspaceView', 'downloadBlob',
71
- 'resolvePluginFrontendBase', 'useExperiment', 'usePlatform', 'useRequest', 'useTheme', 'useToast',
71
+ 'providePluginIdentity', 'resolvePluginFrontendBase', 'useExperiment', 'usePlatform', 'useRequest', 'useTheme', 'useToast',
72
72
  ])
73
73
  })
74
74
 
@@ -490,6 +490,20 @@ describe('useAppExperiment', () => {
490
490
  expect(state.saveLoading.value).toBe(false)
491
491
  })
492
492
 
493
+ it('should show the refetched update time after a successful save', async () => {
494
+ const store = useExperimentStore()
495
+ useAppExperiment({ onSave: vi.fn().mockResolvedValue('Saved') })
496
+ const state = getState()
497
+ store.select(makeExperiment({ updated_at: '2025-01-01T00:00:00Z' }))
498
+
499
+ const fetch = stubExperimentFetch()
500
+ const saving = state.handleSave()
501
+ fetch.resolveWith(makeExperiment({ updated_at: '2026-10-28T09:30:00Z' }))
502
+ await saving
503
+
504
+ expect(state.popover.value.experimentUpdatedAt).toBe('2026-10-28T09:30:00Z')
505
+ })
506
+
493
507
  it('should not set saveSuccessMessage when onSave returns null', async () => {
494
508
  const onSave = vi.fn().mockResolvedValue(null)
495
509
  useAppExperiment({ onSave })
@@ -716,8 +730,9 @@ describe('useAppExperiment', () => {
716
730
  it('exposes live ExperimentPopover props from the provided state', async () => {
717
731
  const saveDisabled = ref(false)
718
732
  const saveDisabledMessage = ref<string | undefined>('Ready to save')
733
+ const dirty = ref(false)
719
734
  const onSave = vi.fn().mockResolvedValue('Saved')
720
- useAppExperiment({ onSave, saveDisabled, saveDisabledMessage })
735
+ useAppExperiment({ onSave, saveDisabled, saveDisabledMessage, dirty })
721
736
  const state = getState()
722
737
 
723
738
  useExperimentStore().select(makeExperiment({
@@ -725,26 +740,37 @@ describe('useAppExperiment', () => {
725
740
  name: 'Dose response',
726
741
  status: 'ongoing',
727
742
  experiment_code: 'EXP-012',
743
+ project: 'legacy-project',
744
+ project_name: 'Hypoxia atlas',
745
+ created_by_user: { id: 3, username: 'jdoe', first_name: 'Jane', last_name: 'Doe' },
746
+ updated_at: '2026-10-28T10:00:00Z',
728
747
  }))
729
748
 
730
749
  expect(state.popover.value).toMatchObject({
731
750
  experimentName: 'Dose response',
732
751
  experimentCode: 'EXP-012',
733
752
  experimentStatus: 'ongoing',
753
+ experimentId: 12,
754
+ experimentProject: 'Hypoxia atlas',
755
+ experimentOwner: 'Jane Doe',
756
+ experimentUpdatedAt: '2026-10-28T10:00:00Z',
734
757
  isResolving: false,
735
758
  showSave: true,
736
759
  showDetach: true,
737
760
  saveDisabled: false,
738
761
  saveDisabledMessage: 'Ready to save',
739
762
  saveLoading: false,
763
+ dirty: false,
740
764
  })
741
765
  expect(state.popover.value.error).toBeUndefined()
742
766
 
743
767
  saveDisabled.value = true
744
768
  saveDisabledMessage.value = 'Locked'
769
+ dirty.value = true
745
770
 
746
771
  expect(state.popover.value.saveDisabled).toBe(true)
747
772
  expect(state.popover.value.saveDisabledMessage).toBe('Locked')
773
+ expect(state.popover.value.dirty).toBe(true)
748
774
 
749
775
  await state.handleSave()
750
776
 
@@ -2,14 +2,16 @@ import { describe, expect, it } from 'vitest'
2
2
  import { useCalendarGrid } from '../../composables/useCalendarGrid'
3
3
 
4
4
  describe('useCalendarGrid', () => {
5
- it('builds a stable 42-day month grid with previous and next month padding', () => {
5
+ it('builds a stable Monday-first 42-day month grid with previous and next month padding', () => {
6
6
  const calendar = useCalendarGrid()
7
7
  calendar.setCurrentMonthFromDate(new Date('2026-05-07T12:00:00'))
8
8
 
9
- expect(calendar.weekDays).toEqual(['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'])
9
+ expect(calendar.weekDays).toEqual(['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'])
10
10
  expect(calendar.monthYear.value).toBe('May 2026')
11
11
  expect(calendar.calendarDays.value).toHaveLength(42)
12
12
  expect(calendar.calendarDays.value[0].date.getMonth()).toBe(3)
13
+ expect(calendar.calendarDays.value[0].date.getDate()).toBe(27)
14
+ expect(calendar.calendarDays.value[0].date.getDay()).toBe(1)
13
15
  expect(calendar.calendarDays.value.some(day => day.isCurrentMonth && day.date.getDate() === 7)).toBe(true)
14
16
  })
15
17
 
@@ -194,6 +194,117 @@ describe('useExperimentSelector', () => {
194
194
  ])
195
195
  })
196
196
 
197
+ it('should send the scope filters but not status to the stats route when counting', async () => {
198
+ fakeResponse = { total: 3, by_status: { planned: 1, ongoing: 2, completed: 0, cancelled: 0 } } as never
199
+ const hook = useExperimentSelector()
200
+ Object.assign(hook.filters, { search: 'glu', project: 'Screen', mine: true, status: 'planned' })
201
+
202
+ await hook.fetchStatusCounts()
203
+
204
+ const url = new URL(recordedUrls[0], 'http://mint.test')
205
+ expect(url.pathname).toBe('/experiments/stats')
206
+ expect(Object.fromEntries(url.searchParams)).toEqual({ search: 'glu', project: 'Screen', mine: 'true' })
207
+ expect(hook.statusCounts.value).toEqual({ planned: 1, ongoing: 2, completed: 0, cancelled: 0 })
208
+ })
209
+
210
+ it('should send mine=true on the list request when the mine filter is set', async () => {
211
+ const hook = useExperimentSelector()
212
+ hook.filters.mine = true
213
+
214
+ await hook.fetch()
215
+
216
+ expect(recordedUrls[0]).toContain('mine=true')
217
+ })
218
+
219
+ it('should sum the per-type stats when several experiment types are allowed', async () => {
220
+ vi.spyOn(useApi().client, 'get').mockImplementation(async (url: string) => {
221
+ recordedUrls.push(url)
222
+ const type = new URL(url, 'http://mint.test').searchParams.get('experiment_type')
223
+ return { data: { total: 2, by_status: { planned: type === 'drug-response' ? 2 : 0, ongoing: type === 'metabolomics' ? 2 : 0 } } }
224
+ })
225
+ const hook = useExperimentSelector({ allowedExperimentTypes: ['drug-response', 'metabolomics'] })
226
+
227
+ await hook.fetchStatusCounts()
228
+
229
+ expect(recordedUrls).toHaveLength(2)
230
+ expect(hook.statusCounts.value).toEqual({ planned: 2, ongoing: 2 })
231
+ })
232
+
233
+ it('should not query the stats route when the selected type is not allowed', async () => {
234
+ const hook = useExperimentSelector({ experimentType: 'proteomics', allowedExperimentTypes: ['drug-response'] })
235
+
236
+ await hook.fetchStatusCounts()
237
+
238
+ expect(recordedUrls).toEqual([])
239
+ expect(hook.statusCounts.value).toEqual({})
240
+ })
241
+
242
+ it('should refetch status counts with the list when a filter or the search changes and counts are on', async () => {
243
+ vi.useFakeTimers()
244
+ const hook = useExperimentSelector({ withStatusCounts: true })
245
+ const statsUrls = () => recordedUrls.filter(url => url.includes('/experiments/stats'))
246
+
247
+ hook.filters.project = 'Screen'
248
+ await nextTick()
249
+
250
+ expect(statsUrls()).toHaveLength(1)
251
+ expect(recordedUrls.filter(url => url.includes('/experiments?'))).toHaveLength(1)
252
+
253
+ hook.filters.search = 'glu'
254
+ await vi.advanceTimersByTimeAsync(300)
255
+
256
+ expect(statsUrls()).toHaveLength(2)
257
+ expect(statsUrls()[1]).toContain('search=glu')
258
+ })
259
+
260
+ it.each([
261
+ ['status', (hook: ReturnType<typeof useExperimentSelector>) => { hook.filters.status = 'planned' }, 1],
262
+ ['sort', (hook: ReturnType<typeof useExperimentSelector>) => { hook.sortKey.value = 'name:asc' }, 1],
263
+ ['project', (hook: ReturnType<typeof useExperimentSelector>) => { hook.filters.project = 'Screen' }, 2],
264
+ ] as const)('should refetch status counts on a %s change only when it changes the counts', async (_name, change, statsCalls) => {
265
+ // One body that serves both routes, so the first stats request succeeds.
266
+ fakeResponse = { ...fakeResponse, by_status: { planned: 1 } } as never
267
+ const hook = useExperimentSelector({ withStatusCounts: true })
268
+ await hook.fetchStatusCounts()
269
+
270
+ change(hook)
271
+ await nextTick()
272
+
273
+ expect(recordedUrls.filter(url => url.includes('/experiments/stats'))).toHaveLength(statsCalls)
274
+ })
275
+
276
+ it('should refetch status counts on a status change when the previous stats request failed', async () => {
277
+ const stats = vi.fn()
278
+ .mockRejectedValueOnce(new Error('offline'))
279
+ .mockResolvedValue({ data: { total: 1, by_status: { planned: 1 } } })
280
+ vi.spyOn(useApi().client, 'get').mockImplementation(async (url: string) =>
281
+ url.includes('/experiments/stats') ? stats() : { data: fakeResponse })
282
+ const hook = useExperimentSelector({ withStatusCounts: true })
283
+ await hook.fetchStatusCounts()
284
+ expect(hook.statusCounts.value).toEqual({})
285
+
286
+ hook.filters.status = 'planned'
287
+ await nextTick()
288
+
289
+ expect(stats).toHaveBeenCalledTimes(2)
290
+ await vi.waitFor(() => expect(hook.statusCounts.value).toEqual({ planned: 1 }))
291
+ })
292
+
293
+ it('should refetch status counts when a status change flushes a pending search', async () => {
294
+ vi.useFakeTimers()
295
+ const hook = useExperimentSelector({ withStatusCounts: true })
296
+ await hook.fetchStatusCounts()
297
+ const statsUrls = () => recordedUrls.filter(url => url.includes('/experiments/stats'))
298
+
299
+ hook.filters.search = 'glu'
300
+ await nextTick()
301
+ hook.filters.status = 'planned'
302
+ await nextTick()
303
+
304
+ expect(statsUrls()).toHaveLength(2)
305
+ expect(statsUrls()[1]).toContain('search=glu')
306
+ })
307
+
197
308
  it('loadMore appends the next page', async () => {
198
309
  const hook = useExperimentSelector({ limit: 1 })
199
310