@morscherlab/mint-sdk 1.4.0-beta.1 → 1.4.0-beta.10

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 (261) hide show
  1. package/dist/{ActionMenu-CScINahU.js → AlertBox-B2Bgr3ei.js} +40 -209
  2. package/dist/AlertBox-B2Bgr3ei.js.map +1 -0
  3. package/dist/{AppSidebar-BmiiPfz9.js → AppSidebar-mvm--GxN.js} +161 -110
  4. package/dist/AppSidebar-mvm--GxN.js.map +1 -0
  5. package/dist/{ConfirmDialog-CJ7hqYUP.js → ConfirmDialog-nbJj51is.js} +2 -2
  6. package/dist/{ConfirmDialog-CJ7hqYUP.js.map → ConfirmDialog-nbJj51is.js.map} +1 -1
  7. package/dist/ExperimentPopover-5T1fAq2L.js +337 -0
  8. package/dist/ExperimentPopover-5T1fAq2L.js.map +1 -0
  9. package/dist/{ExperimentSelectorModal-D-9z7cNx.js → ExperimentSelectorModal-65peG4Up.js} +198 -181
  10. package/dist/ExperimentSelectorModal-65peG4Up.js.map +1 -0
  11. package/dist/{SegmentedControl-DERBN-Xd.js → SegmentedControl-BtnnjWzs.js} +2 -2
  12. package/dist/{SegmentedControl-DERBN-Xd.js.map → SegmentedControl-BtnnjWzs.js.map} +1 -1
  13. package/dist/{SettingsModal-CSLE31yg.js → SettingsModal-DO3IZ7qz.js} +9 -9
  14. package/dist/SettingsModal-DO3IZ7qz.js.map +1 -0
  15. package/dist/components/AppSidebar.vue.d.ts +1 -1
  16. package/dist/components/ArtifactSaveDialog.vue.d.ts +9 -1
  17. package/dist/components/AuditTrail.vue.d.ts +1 -1
  18. package/dist/components/BaseSelect.vue.d.ts +2 -2
  19. package/dist/components/ChartContainer.vue.d.ts +2 -0
  20. package/dist/components/DatePicker.vue.d.ts +1 -1
  21. package/dist/components/ExperimentPopover.vue.d.ts +18 -0
  22. package/dist/components/ExperimentSelectorModal.vue.d.ts +0 -4
  23. package/dist/components/FilePicker.vue.d.ts +1 -1
  24. package/dist/components/FileUploader.vue.d.ts +2 -2
  25. package/dist/components/PluginIcon.vue.d.ts +2 -2
  26. package/dist/components/SampleSelector.vue.d.ts +6 -6
  27. package/dist/components/SequenceProgressBar.vue.d.ts +3 -0
  28. package/dist/components/StepWizard.vue.d.ts +3 -0
  29. package/dist/components/internal/AutoGroupModalInternal.vue.d.ts +1 -1
  30. package/dist/components/internal/DatePickerTimeInternal.vue.d.ts +1 -1
  31. package/dist/components/internal/ResourcePickerListInternal.vue.d.ts +2 -7
  32. package/dist/components/internal/SelectMultipleInternal.vue.d.ts +4 -0
  33. package/dist/composables/createClient.d.ts +2 -0
  34. package/dist/composables/index.d.ts +1 -0
  35. package/dist/composables/pluginIdentity.d.ts +24 -0
  36. package/dist/composables/pluginWorkspaceModel.d.ts +2 -2
  37. package/dist/composables/useAppExperiment.d.ts +8 -0
  38. package/dist/composables/useCalendarGrid.d.ts +1 -1
  39. package/dist/composables/useExperimentSelector.d.ts +6 -2
  40. package/dist/composables/usePlatform.d.ts +1 -0
  41. package/dist/{controlSchemaModel-CP5-_nAj.js → controlSchemaModel-B8CTXagP.js} +2 -2
  42. package/dist/{controlSchemaModel-CP5-_nAj.js.map → controlSchemaModel-B8CTXagP.js.map} +1 -1
  43. package/dist/{data-C2PwI_dn.js → data-CgILxDLW.js} +178 -124
  44. package/dist/data-CgILxDLW.js.map +1 -0
  45. package/dist/data.js +8 -8
  46. package/dist/{experimentStatus-BZmU4czV.js → experimentStatus-Cqp6qNrR.js} +3 -3
  47. package/dist/{experimentStatus-BZmU4czV.js.map → experimentStatus-Cqp6qNrR.js.map} +1 -1
  48. package/dist/index.js +19 -19
  49. package/dist/{install-DAID6c2-.js → install-CsPFJSx3.js} +15 -14
  50. package/dist/{install-DAID6c2-.js.map → install-CsPFJSx3.js.map} +1 -1
  51. package/dist/install.js +1 -1
  52. package/dist/{lab-DLwRl_Ql.js → lab-MHzp-_J8.js} +128 -147
  53. package/dist/lab-MHzp-_J8.js.map +1 -0
  54. package/dist/lab.js +4 -3
  55. package/dist/{records-BzTcxekG.js → records-B6gpM_3e.js} +472 -274
  56. package/dist/records-B6gpM_3e.js.map +1 -0
  57. package/dist/{runtime-D6gVat6r.js → runtime-8bs8EogX.js} +27 -31
  58. package/dist/runtime-8bs8EogX.js.map +1 -0
  59. package/dist/runtime.js +6 -6
  60. package/dist/{shell-5ju97nOS.js → shell-CywENwBT.js} +5 -5
  61. package/dist/{shell-5ju97nOS.js.map → shell-CywENwBT.js.map} +1 -1
  62. package/dist/shell.js +5 -5
  63. package/dist/styles.css +1606 -1666
  64. package/dist/types/componentWorkflowTypes.d.ts +2 -0
  65. package/dist/types/generated/platformContext.d.ts +1 -0
  66. package/dist/types/generated-ui.d.ts +4 -0
  67. package/dist/{BaseModal-DfRIuVoj.js → useDropdownState-8yWMu-7_.js} +60 -2
  68. package/dist/useDropdownState-8yWMu-7_.js.map +1 -0
  69. package/dist/{useExperimentSamples-CxcnxZ8h.js → useExperimentSamples-DHnxKFAF.js} +3 -3
  70. package/dist/{useExperimentSamples-CxcnxZ8h.js.map → useExperimentSamples-DHnxKFAF.js.map} +1 -1
  71. package/dist/{useListReorder-CcbEYxX-.js → useListReorder-BitdUhxn.js} +109 -277
  72. package/dist/useListReorder-BitdUhxn.js.map +1 -0
  73. package/dist/{useRackEditor-BD5NIo5s.js → useRackEditor-DiwCt77M.js} +71 -57
  74. package/dist/useRackEditor-DiwCt77M.js.map +1 -0
  75. package/dist/{useRealtime-PalKue84.js → useRealtime-C3iy9QkU.js} +7 -4
  76. package/dist/useRealtime-C3iy9QkU.js.map +1 -0
  77. package/dist/useRequest-D506eHi8.js +244 -0
  78. package/dist/useRequest-D506eHi8.js.map +1 -0
  79. package/dist/utils/time.d.ts +7 -0
  80. package/package.json +1 -1
  81. package/src/__tests__/components/AppTopBar.test.ts +51 -2
  82. package/src/__tests__/components/AppTopBarPillNav.test.ts +35 -0
  83. package/src/__tests__/components/ArtifactSaveDialog.test.ts +78 -0
  84. package/src/__tests__/components/BaseSelect.multiple.test.ts +87 -32
  85. package/src/__tests__/components/BaseSelect.test.ts +2 -1
  86. package/src/__tests__/components/BatchProgressList.test.ts +16 -0
  87. package/src/__tests__/components/CalendarGridPanel.test.ts +2 -2
  88. package/src/__tests__/components/ChartContainer.test.ts +11 -0
  89. package/src/__tests__/components/ChemicalFormula.test.ts +6 -3
  90. package/src/__tests__/components/DatePicker.datetime.test.ts +6 -0
  91. package/src/__tests__/components/DatePicker.test.ts +21 -0
  92. package/src/__tests__/components/DatePicker.time.test.ts +2 -2
  93. package/src/__tests__/components/EmptyState.test.ts +1 -1
  94. package/src/__tests__/components/ExperimentPopover.test.ts +125 -16
  95. package/src/__tests__/components/ExperimentSelectorModal.test.ts +119 -13
  96. package/src/__tests__/components/GeneratedPluginView.test.ts +22 -1
  97. package/src/__tests__/components/InstrumentStateBadge.test.ts +4 -4
  98. package/src/__tests__/components/PluginWorkspaceView.test.ts +7 -0
  99. package/src/__tests__/components/ResourcePickerListInternal.test.ts +0 -20
  100. package/src/__tests__/components/SampleSelector.test.ts +13 -8
  101. package/src/__tests__/components/SequenceProgressBar.test.ts +12 -0
  102. package/src/__tests__/components/StepWizard.test.ts +11 -0
  103. package/src/__tests__/components/WellPlate.decoration.test.ts +11 -4
  104. package/src/__tests__/components/deprecationTags.test.ts +1 -1
  105. package/src/__tests__/composables/useAppExperiment.test.ts +27 -1
  106. package/src/__tests__/composables/useCalendarGrid.test.ts +4 -2
  107. package/src/__tests__/composables/useExperimentSelector.test.ts +111 -0
  108. package/src/__tests__/styles/baseControlsRules.test.ts +6 -2
  109. package/src/__tests__/styles/controlRefreshRules.test.ts +0 -6
  110. package/src/__tests__/styles/cssTokens.test.ts +2 -2
  111. package/src/__tests__/styles/menusShellRefresh.test.ts +14 -10
  112. package/src/__tests__/utils/plotlyTemplate.test.ts +4 -4
  113. package/src/__tests__/utils/time.test.ts +18 -1
  114. package/src/components/AdductText.story.vue +7 -0
  115. package/src/components/AdductText.vue +1 -1
  116. package/src/components/AlertBox.story.vue +2 -2
  117. package/src/components/AlertBox.vue +18 -16
  118. package/src/components/AppSidebar.story.vue +0 -28
  119. package/src/components/AppTopBar.story.vue +47 -12
  120. package/src/components/AppTopBar.vue +118 -114
  121. package/src/components/ArtifactSaveDialog.story.vue +23 -0
  122. package/src/components/ArtifactSaveDialog.vue +102 -12
  123. package/src/components/AuditTrail.story.vue +2 -1
  124. package/src/components/AuditTrail.vue +2 -21
  125. package/src/components/Avatar.vue +10 -14
  126. package/src/components/BaseSelect.multiple.story.vue +13 -2
  127. package/src/components/BaseSelect.vue +4 -3
  128. package/src/components/BatchProgressList.vue +35 -25
  129. package/src/components/ChartContainer.story.vue +2 -1
  130. package/src/components/ChartContainer.vue +8 -6
  131. package/src/components/ConfirmDialog.story.vue +1 -1
  132. package/src/components/EmptyState.vue +1 -1
  133. package/src/components/ExperimentPopover.story.vue +165 -53
  134. package/src/components/ExperimentPopover.vue +169 -187
  135. package/src/components/ExperimentSelectorModal.story.vue +40 -36
  136. package/src/components/ExperimentSelectorModal.vue +124 -110
  137. package/src/components/FormField.story.vue +8 -48
  138. package/src/components/GeneratedPluginView.vue +7 -1
  139. package/src/components/InstrumentStateBadge.vue +1 -1
  140. package/src/components/JobsStatusTray.vue +1 -1
  141. package/src/components/LoadingSpinner.vue +1 -1
  142. package/src/components/NumberInput.vue +3 -6
  143. package/src/components/PluginIcon.story.vue +2 -10
  144. package/src/components/PluginIcon.vue +2 -2
  145. package/src/components/PluginWorkspaceView.story.vue +1 -1
  146. package/src/components/RuntimeUpdateNotice.vue +1 -1
  147. package/src/components/SampleSelector.story.vue +1 -1
  148. package/src/components/SampleSelector.vue +27 -37
  149. package/src/components/SampleSelectorGroupNode.vue +1 -1
  150. package/src/components/SectionCard.story.vue +2 -2
  151. package/src/components/SequenceProgressBar.story.vue +9 -0
  152. package/src/components/SequenceProgressBar.vue +6 -1
  153. package/src/components/SettingsModal.schema.ts +1 -1
  154. package/src/components/SettingsModal.vue +2 -2
  155. package/src/components/SmartGroupFieldRecipe.story.vue +1 -1
  156. package/src/components/SmartGroupManual.story.vue +1 -1
  157. package/src/components/StepWizard.story.vue +33 -0
  158. package/src/components/StepWizard.vue +8 -1
  159. package/src/components/WellPlate.story.vue +3 -1
  160. package/src/components/WellPlate.vue +7 -0
  161. package/src/components/internal/AppTopBarPillNavInternal.vue +33 -2
  162. package/src/components/internal/DatePickerDateInternal.vue +16 -40
  163. package/src/components/internal/DatePickerDateTimeInternal.vue +3 -13
  164. package/src/components/internal/DatePickerTimeInternal.vue +5 -8
  165. package/src/components/internal/ResourcePickerListInternal.vue +45 -85
  166. package/src/components/internal/SelectMultipleInternal.vue +182 -65
  167. package/src/components/internal/SelectSearchableInternal.vue +2 -3
  168. package/src/composables/createClient.ts +2 -0
  169. package/src/composables/index.ts +1 -0
  170. package/src/composables/pluginIdentity.ts +31 -0
  171. package/src/composables/pluginWorkspaceModel.ts +2 -2
  172. package/src/composables/useAppExperiment.ts +20 -0
  173. package/src/composables/useCalendarGrid.ts +4 -3
  174. package/src/composables/useExperimentSelector.ts +77 -31
  175. package/src/composables/usePluginWorkspace.ts +1 -1
  176. package/src/styles/components/adduct-text.css +13 -5
  177. package/src/styles/components/alert-box.css +39 -58
  178. package/src/styles/components/app-layout.css +6 -5
  179. package/src/styles/components/app-page-selector.css +3 -1
  180. package/src/styles/components/app-pill-nav.css +11 -12
  181. package/src/styles/components/app-sidebar.css +9 -18
  182. package/src/styles/components/app-top-bar.css +57 -55
  183. package/src/styles/components/artifact-save-dialog.css +153 -2
  184. package/src/styles/components/audit-trail.css +16 -19
  185. package/src/styles/components/avatar.css +6 -4
  186. package/src/styles/components/batch-progress-list.css +38 -36
  187. package/src/styles/components/button.css +28 -59
  188. package/src/styles/components/calendar.css +12 -12
  189. package/src/styles/components/chart-container.css +34 -23
  190. package/src/styles/components/checkbox.css +2 -12
  191. package/src/styles/components/collapsible-card.css +27 -29
  192. package/src/styles/components/confirm-dialog.css +13 -14
  193. package/src/styles/components/dataframe.css +12 -4
  194. package/src/styles/components/date-picker.css +80 -92
  195. package/src/styles/components/datetime-picker.css +31 -43
  196. package/src/styles/components/divider.css +20 -20
  197. package/src/styles/components/dose-calculator.css +3 -3
  198. package/src/styles/components/empty-state.css +20 -17
  199. package/src/styles/components/experiment-code-badge.css +2 -0
  200. package/src/styles/components/experiment-popover.css +174 -329
  201. package/src/styles/components/experiment-selector-modal.css +75 -56
  202. package/src/styles/components/file-picker.css +3 -3
  203. package/src/styles/components/file-uploader.css +38 -49
  204. package/src/styles/components/form-builder.css +3 -3
  205. package/src/styles/components/formula-input.css +1 -1
  206. package/src/styles/components/icon-button.css +18 -24
  207. package/src/styles/components/input.css +5 -4
  208. package/src/styles/components/instrument-monitor.css +13 -15
  209. package/src/styles/components/jobs-status-tray.css +31 -25
  210. package/src/styles/components/modal.css +2 -4
  211. package/src/styles/components/molecule-input.css +2 -2
  212. package/src/styles/components/multi-select.css +65 -117
  213. package/src/styles/components/number-input.css +26 -19
  214. package/src/styles/components/pill.css +3 -2
  215. package/src/styles/components/plate-editor.css +0 -1
  216. package/src/styles/components/plugin-icon.css +26 -23
  217. package/src/styles/components/popover.css +2 -2
  218. package/src/styles/components/presence-avatars.css +1 -1
  219. package/src/styles/components/protocol-step-editor.css +1 -3
  220. package/src/styles/components/resource-card.css +2 -2
  221. package/src/styles/components/resource-picker-list.css +6 -57
  222. package/src/styles/components/sample-hierarchy-tree.css +4 -4
  223. package/src/styles/components/sample-selector.css +95 -79
  224. package/src/styles/components/schedule-calendar.css +8 -8
  225. package/src/styles/components/searchable-select.css +12 -12
  226. package/src/styles/components/section-card.css +28 -47
  227. package/src/styles/components/segmented-control.css +18 -15
  228. package/src/styles/components/select.css +3 -3
  229. package/src/styles/components/sequence-input.css +2 -2
  230. package/src/styles/components/sequence-progress-bar.css +17 -8
  231. package/src/styles/components/smart-group.css +18 -18
  232. package/src/styles/components/step-wizard.css +52 -16
  233. package/src/styles/components/tabs.css +2 -2
  234. package/src/styles/components/textarea.css +3 -3
  235. package/src/styles/components/time-picker.css +36 -21
  236. package/src/styles/components/toast.css +12 -10
  237. package/src/styles/components/toggle.css +1 -11
  238. package/src/styles/components/tooltip.css +10 -9
  239. package/src/styles/components/unit-input.css +8 -7
  240. package/src/styles/components/well-plate.css +9 -2
  241. package/src/styles/variables.css +106 -75
  242. package/src/types/componentWorkflowTypes.ts +2 -0
  243. package/src/types/generated/platformContext.ts +1 -0
  244. package/src/types/generated-ui.ts +4 -0
  245. package/src/utils/time.ts +21 -0
  246. package/dist/ActionMenu-CScINahU.js.map +0 -1
  247. package/dist/AppSidebar-BmiiPfz9.js.map +0 -1
  248. package/dist/BaseModal-DfRIuVoj.js.map +0 -1
  249. package/dist/ExperimentPopover-C4sGw-is.js +0 -359
  250. package/dist/ExperimentPopover-C4sGw-is.js.map +0 -1
  251. package/dist/ExperimentSelectorModal-D-9z7cNx.js.map +0 -1
  252. package/dist/SettingsModal-CSLE31yg.js.map +0 -1
  253. package/dist/data-C2PwI_dn.js.map +0 -1
  254. package/dist/lab-DLwRl_Ql.js.map +0 -1
  255. package/dist/records-BzTcxekG.js.map +0 -1
  256. package/dist/runtime-D6gVat6r.js.map +0 -1
  257. package/dist/useListReorder-CcbEYxX-.js.map +0 -1
  258. package/dist/useRackEditor-BD5NIo5s.js.map +0 -1
  259. package/dist/useRealtime-PalKue84.js.map +0 -1
  260. package/dist/useRequest-D5JDfsgZ.js +0 -76
  261. package/dist/useRequest-D5JDfsgZ.js.map +0 -1
@@ -7,18 +7,15 @@
7
7
  * <ExperimentSelectorModal v-model:open="open" :allowed-experiment-types="['mass-spectrometry']" />
8
8
  * ```
9
9
  */
10
- import { ref, computed, watch } from 'vue'
11
- import type { ModalSize, ExperimentSummary, ExperimentFilters } from '../types'
10
+ import { ref, computed, watch, type Component } from 'vue'
11
+ import { ChevronDown, CircleDot, Folder, Shapes, User } from 'lucide-vue-next'
12
+ import type { ModalSize, ExperimentSummary, ExperimentFilters, ActionMenuItem, Tone } from '../types'
12
13
  import { useExperimentSelector } from '../composables/useExperimentSelector'
13
14
  import { useExperimentStore } from '../stores/experiment'
14
- import {
15
- formatExperimentDate,
16
- DATE_PRESET_OPTIONS,
17
- SORT_OPTIONS,
18
- } from '../composables/experiment-utils'
15
+ import { formatExperimentDate } from '../composables/experiment-utils'
19
16
  import * as experimentStatus from '../utils/experimentStatus'
20
17
  import BaseModal from './BaseModal.vue'
21
- import BaseSelect from './BaseSelect.vue'
18
+ import ActionMenu from './ActionMenu.vue'
22
19
  import ResourcePickerListInternal from './internal/ResourcePickerListInternal.vue'
23
20
 
24
21
  interface Props {
@@ -28,16 +25,12 @@ interface Props {
28
25
  currentExperimentId?: number | null
29
26
  title?: string
30
27
  size?: ModalSize
31
- groupByProject?: boolean
32
- showFilters?: boolean
33
28
  }
34
29
 
35
30
  const props = withDefaults(defineProps<Props>(), {
36
31
  currentExperimentId: null,
37
32
  title: 'Select Experiment',
38
- size: 'full',
39
- groupByProject: false,
40
- showFilters: false,
33
+ size: 'lg',
41
34
  })
42
35
 
43
36
  // Selection is committed to the shared experiment store rather than emitted.
@@ -54,55 +47,102 @@ const {
54
47
  filters,
55
48
  isLoading,
56
49
  error,
57
- sortKey,
58
50
  experimentTypes,
59
51
  projects,
60
- groupedByProject,
52
+ statusCounts,
61
53
  fetch: fetchExperiments,
62
54
  fetchFilterOptions,
55
+ fetchStatusCounts,
63
56
  } = useExperimentSelector({
64
57
  experimentType: props.experimentType,
65
58
  allowedExperimentTypes: props.allowedExperimentTypes,
59
+ withStatusCounts: true,
66
60
  })
67
61
 
68
- const showAdvanced = ref(props.showFilters)
69
- const groupToggle = ref(props.groupByProject)
70
- // Re-mounting the list on open clears its collapsed groups and keyboard highlight.
62
+ // Re-mounting the list on open clears its keyboard highlight.
71
63
  const openCount = ref(0)
72
64
 
73
- // Track whether any advanced filter is active (for badge dot)
74
- const hasActiveAdvancedFilters = computed(() =>
75
- !!(filters.project || filters.experimentType || filters.datePreset || sortKey.value !== 'created_at:desc'),
76
- )
65
+ const STATUS_ORDER = ['ongoing', 'planned', 'completed', 'cancelled'] as const
77
66
 
78
- // Build type filter options from fetched experiment types
79
- const typeFilterOptions = computed(() => [
80
- { value: '', label: 'All types' },
81
- ...experimentTypes.value.map(t => ({ value: t.value, label: t.label })),
82
- ])
83
67
  const canChooseExperimentType = computed(() =>
84
- !props.experimentType && typeFilterOptions.value.length > 2,
68
+ !props.experimentType && experimentTypes.value.length > 1,
85
69
  )
86
70
 
87
- // Build project filter options from fetched projects
88
- const projectFilterOptions = computed(() => [
89
- { value: '', label: 'All projects' },
90
- ...projects.value,
91
- ])
71
+ type FilterKey = 'status' | 'project' | 'experimentType'
72
+
73
+ interface FilterMenu {
74
+ key: FilterKey
75
+ label: string
76
+ icon: Component
77
+ items: ActionMenuItem[]
78
+ /** Trigger text while the filter is set. */
79
+ activeLabel?: string
80
+ dotTone?: Tone
81
+ }
82
+
83
+ /** Every item carries `checked`, so ActionMenu renders a single-choice menu. */
84
+ function choiceItems(key: FilterKey, anyLabel: string, options: { value: string; label: string }[]): ActionMenuItem[] {
85
+ const current = filters[key] ?? ''
86
+ return [{ value: '', label: anyLabel }, ...options].map(option => ({
87
+ id: option.value,
88
+ label: option.label,
89
+ checked: option.value === current,
90
+ }))
91
+ }
92
+
93
+ const filterMenus = computed<FilterMenu[]>(() => {
94
+ const menus: FilterMenu[] = [{
95
+ key: 'status',
96
+ label: 'Status',
97
+ icon: CircleDot,
98
+ items: choiceItems('status', 'Any status', STATUS_ORDER.map(status => {
99
+ const count = statusCounts.value[status]
100
+ const label = experimentStatus.format(status)
101
+ return { value: status, label: count === undefined ? label : `${label} · ${count}` }
102
+ })),
103
+ activeLabel: filters.status ? experimentStatus.format(filters.status) : undefined,
104
+ dotTone: filters.status ? experimentStatus.tone(filters.status) : undefined,
105
+ }]
106
+ if (projects.value.length) {
107
+ menus.push({
108
+ key: 'project',
109
+ label: 'Project',
110
+ icon: Folder,
111
+ items: choiceItems('project', 'Any project', projects.value),
112
+ activeLabel: filters.project ?? undefined,
113
+ })
114
+ }
115
+ if (canChooseExperimentType.value) {
116
+ menus.push({
117
+ key: 'experimentType',
118
+ label: 'Type',
119
+ icon: Shapes,
120
+ items: choiceItems('experimentType', 'Any type', experimentTypes.value),
121
+ activeLabel: experimentTypes.value.find(t => t.value === filters.experimentType)?.label
122
+ ?? filters.experimentType ?? undefined,
123
+ })
124
+ }
125
+ return menus
126
+ })
92
127
 
93
- const groups = computed(() =>
94
- groupToggle.value
95
- ? groupedByProject.value.map(([label, items]) => ({ label, items }))
96
- : null,
128
+ const activeFilterCount = computed(() =>
129
+ [filters.status, filters.project, filters.experimentType, filters.mine].filter(Boolean).length,
97
130
  )
98
131
 
132
+ function clearFilters() {
133
+ filters.status = undefined
134
+ filters.project = undefined
135
+ filters.experimentType = undefined
136
+ filters.mine = undefined
137
+ }
138
+
99
139
  function describe(experiment: ExperimentSummary) {
100
140
  const owner = experiment.created_by_user
101
141
  return {
102
142
  code: experiment.experiment_code,
103
143
  title: experiment.name,
104
144
  meta: [
105
- ...(groupToggle.value ? [] : [experiment.project_name || experiment.project || '']),
145
+ experiment.project_name || experiment.project || '',
106
146
  owner ? [owner.first_name, owner.last_name].filter(Boolean).join(' ') || owner.username : '',
107
147
  ].filter(Boolean),
108
148
  status: experiment.status
@@ -116,10 +156,6 @@ function setFilter<K extends keyof ExperimentFilters>(key: K, value: string | nu
116
156
  ;(filters as Record<string, unknown>)[key] = String(value) || undefined
117
157
  }
118
158
 
119
- function handleSortChange(value: string | number) {
120
- sortKey.value = String(value) || 'created_at:desc'
121
- }
122
-
123
159
  function handleSelect(experiment: ExperimentSummary) {
124
160
  experimentStore.select(experiment)
125
161
  emit('update:open', false)
@@ -138,6 +174,7 @@ watch(
138
174
  openCount.value++
139
175
  fetchFilterOptions()
140
176
  fetchExperiments()
177
+ fetchStatusCounts()
141
178
  }
142
179
  },
143
180
  { immediate: true },
@@ -155,10 +192,10 @@ watch(
155
192
  :key="openCount"
156
193
  :search="filters.search ?? ''"
157
194
  class="mint-experiment-selector"
195
+ autofocus-search
158
196
  label="Experiments"
159
197
  placeholder="Search experiments…"
160
198
  :items="experiments"
161
- :groups="groups"
162
199
  :item-key="(experiment) => experiment.id"
163
200
  :describe="describe"
164
201
  :selected-key="currentExperimentId"
@@ -169,77 +206,54 @@ watch(
169
206
  @select="handleSelect"
170
207
  >
171
208
  <template #filters>
172
- <div class="mint-experiment-selector__filter-select">
173
- <BaseSelect
174
- :model-value="filters.status ?? ''"
175
- :options="experimentStatus.OPTIONS"
176
- size="sm"
177
- aria-label="Filter by status"
178
- @update:model-value="v => setFilter('status', v)"
179
- />
180
- </div>
181
- <div v-if="canChooseExperimentType" class="mint-experiment-selector__filter-select">
182
- <BaseSelect
183
- :model-value="filters.experimentType ?? ''"
184
- :options="typeFilterOptions"
185
- size="sm"
186
- aria-label="Filter by type"
187
- @update:model-value="v => setFilter('experimentType', v)"
188
- />
189
- </div>
209
+ <ActionMenu
210
+ v-for="menu in filterMenus"
211
+ :key="menu.key"
212
+ :label="menu.label"
213
+ :items="menu.items"
214
+ @select="item => setFilter(menu.key, item.id)"
215
+ >
216
+ <template #trigger="{ open: menuOpen, toggle, keydown }">
217
+ <button
218
+ type="button"
219
+ class="mint-experiment-selector__toggle"
220
+ :class="{ 'mint-experiment-selector__toggle--active': menu.activeLabel }"
221
+ :aria-label="`${menu.label}: ${menu.activeLabel ?? menu.items[0].label}`"
222
+ aria-haspopup="menu"
223
+ :aria-expanded="menuOpen"
224
+ @click="toggle"
225
+ @keydown="keydown"
226
+ >
227
+ <span
228
+ v-if="menu.dotTone"
229
+ class="mint-experiment-selector__dot"
230
+ :class="`mint-experiment-selector__dot--${menu.dotTone}`"
231
+ aria-hidden="true"
232
+ />
233
+ <component :is="menu.icon" v-else :size="13" aria-hidden="true" />
234
+ <span class="mint-experiment-selector__toggle-label">{{ menu.activeLabel ?? menu.label }}</span>
235
+ <ChevronDown :size="12" aria-hidden="true" />
236
+ </button>
237
+ </template>
238
+ </ActionMenu>
190
239
  <button
191
- class="mint-experiment-selector__filters-toggle"
192
- :class="{ 'mint-experiment-selector__filters-toggle--active': hasActiveAdvancedFilters }"
193
240
  type="button"
194
- :aria-expanded="showAdvanced"
195
- @click="showAdvanced = !showAdvanced"
241
+ class="mint-experiment-selector__toggle"
242
+ :class="{ 'mint-experiment-selector__toggle--active': filters.mine }"
243
+ :aria-pressed="!!filters.mine"
244
+ @click="filters.mine = filters.mine ? undefined : true"
196
245
  >
197
- <svg aria-hidden="true" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
198
- <line x1="4" y1="6" x2="20" y2="6" /><line x1="8" y1="12" x2="20" y2="12" /><line x1="12" y1="18" x2="20" y2="18" />
199
- <circle cx="6" cy="12" r="2" /><circle cx="10" cy="18" r="2" /><circle cx="6" cy="6" r="2" />
200
- </svg>
201
- Filters
202
- <span v-if="hasActiveAdvancedFilters" class="mint-experiment-selector__filters-dot" />
246
+ <User :size="13" aria-hidden="true" />
247
+ Mine
248
+ </button>
249
+ <button
250
+ v-if="activeFilterCount"
251
+ type="button"
252
+ class="mint-experiment-selector__clear-filters"
253
+ @click="clearFilters"
254
+ >
255
+ Clear {{ activeFilterCount }}
203
256
  </button>
204
- </template>
205
- <template #advanced>
206
- <div v-if="showAdvanced" class="mint-experiment-selector__filters-advanced">
207
- <div v-if="projectFilterOptions.length > 1" class="mint-experiment-selector__filter-select">
208
- <BaseSelect
209
- :model-value="filters.project ?? ''"
210
- :options="projectFilterOptions"
211
- size="sm"
212
- aria-label="Filter by project"
213
- @update:model-value="v => setFilter('project', v)"
214
- />
215
- </div>
216
- <div class="mint-experiment-selector__filter-select">
217
- <BaseSelect
218
- :model-value="filters.datePreset ?? ''"
219
- :options="DATE_PRESET_OPTIONS"
220
- size="sm"
221
- aria-label="Filter by date"
222
- @update:model-value="v => setFilter('datePreset', v)"
223
- />
224
- </div>
225
- <div class="mint-experiment-selector__filter-select">
226
- <BaseSelect
227
- :model-value="sortKey"
228
- :options="SORT_OPTIONS"
229
- size="sm"
230
- aria-label="Sort"
231
- @update:model-value="handleSortChange"
232
- />
233
- </div>
234
- <label class="mint-experiment-selector__group-toggle">
235
- <input
236
- v-model="groupToggle"
237
- type="checkbox"
238
- class="mint-experiment-selector__group-checkbox"
239
- />
240
- Group by project
241
- </label>
242
- </div>
243
257
  </template>
244
258
  </ResourcePickerListInternal>
245
259
 
@@ -15,12 +15,7 @@ import BaseInput from './BaseInput.vue'
15
15
  :required="state.required"
16
16
  :html-for="state.htmlFor"
17
17
  >
18
- <input
19
- :id="state.htmlFor"
20
- type="text"
21
- placeholder="Enter value..."
22
- style="width: 100%; padding: 0.5rem 0.75rem; border: 1px solid var(--border-color, #e2e8f0); border-radius: 0.375rem; font-size: 0.875rem; background: var(--bg-primary, #fff); color: var(--text-primary, #1e293b); box-sizing: border-box;"
23
- />
18
+ <BaseInput :id="state.htmlFor" placeholder="Enter value..." />
24
19
  </FormField>
25
20
  </div>
26
21
  </template>
@@ -39,12 +34,7 @@ import BaseInput from './BaseInput.vue'
39
34
  <Variant title="With Label">
40
35
  <div style="padding: 2rem; max-width: 400px; margin: 0 auto;">
41
36
  <FormField label="Sample Name" html-for="sample-name">
42
- <input
43
- id="sample-name"
44
- type="text"
45
- placeholder="e.g., SAMPLE-001"
46
- style="width: 100%; padding: 0.5rem 0.75rem; border: 1px solid var(--border-color, #e2e8f0); border-radius: 0.375rem; font-size: 0.875rem; background: var(--bg-primary, #fff); color: var(--text-primary, #1e293b); box-sizing: border-box;"
47
- />
37
+ <BaseInput id="sample-name" placeholder="e.g., SAMPLE-001" />
48
38
  </FormField>
49
39
  </div>
50
40
  </Variant>
@@ -52,12 +42,7 @@ import BaseInput from './BaseInput.vue'
52
42
  <Variant title="Required Field">
53
43
  <div style="padding: 2rem; max-width: 400px; margin: 0 auto;">
54
44
  <FormField label="Experiment ID" :required="true" html-for="exp-id">
55
- <input
56
- id="exp-id"
57
- type="text"
58
- placeholder="Required field"
59
- style="width: 100%; padding: 0.5rem 0.75rem; border: 1px solid var(--border-color, #e2e8f0); border-radius: 0.375rem; font-size: 0.875rem; background: var(--bg-primary, #fff); color: var(--text-primary, #1e293b); box-sizing: border-box;"
60
- />
45
+ <BaseInput id="exp-id" placeholder="Required field" />
61
46
  </FormField>
62
47
  </div>
63
48
  </Variant>
@@ -65,12 +50,7 @@ import BaseInput from './BaseInput.vue'
65
50
  <Variant title="With Hint">
66
51
  <div style="padding: 2rem; max-width: 400px; margin: 0 auto;">
67
52
  <FormField label="Concentration" hint="Enter value in micromolar (uM)" html-for="conc">
68
- <input
69
- id="conc"
70
- type="number"
71
- placeholder="0.00"
72
- style="width: 100%; padding: 0.5rem 0.75rem; border: 1px solid var(--border-color, #e2e8f0); border-radius: 0.375rem; font-size: 0.875rem; background: var(--bg-primary, #fff); color: var(--text-primary, #1e293b); box-sizing: border-box;"
73
- />
53
+ <BaseInput id="conc" inputmode="decimal" placeholder="0.00" />
74
54
  </FormField>
75
55
  </div>
76
56
  </Variant>
@@ -78,12 +58,7 @@ import BaseInput from './BaseInput.vue'
78
58
  <Variant title="With Error">
79
59
  <div style="padding: 2rem; max-width: 400px; margin: 0 auto;">
80
60
  <FormField label="Plate Barcode" error="Barcode format is invalid. Expected: PLT-XXXX-XXXX" html-for="barcode">
81
- <input
82
- id="barcode"
83
- type="text"
84
- value="invalid"
85
- style="width: 100%; padding: 0.5rem 0.75rem; border: 1px solid #ef4444; border-radius: 0.375rem; font-size: 0.875rem; background: var(--bg-primary, #fff); color: var(--text-primary, #1e293b); box-sizing: border-box;"
86
- />
61
+ <BaseInput id="barcode" model-value="invalid" invalid />
87
62
  </FormField>
88
63
  </div>
89
64
  </Variant>
@@ -91,28 +66,13 @@ import BaseInput from './BaseInput.vue'
91
66
  <Variant title="Form Example">
92
67
  <div style="padding: 2rem; max-width: 400px; margin: 0 auto; display: flex; flex-direction: column; gap: 1rem;">
93
68
  <FormField label="Experiment Name" :required="true" html-for="form-name">
94
- <input
95
- id="form-name"
96
- type="text"
97
- placeholder="Enter experiment name"
98
- style="width: 100%; padding: 0.5rem 0.75rem; border: 1px solid var(--border-color, #e2e8f0); border-radius: 0.375rem; font-size: 0.875rem; background: var(--bg-primary, #fff); color: var(--text-primary, #1e293b); box-sizing: border-box;"
99
- />
69
+ <BaseInput id="form-name" placeholder="Enter experiment name" />
100
70
  </FormField>
101
71
  <FormField label="Description" hint="Optional description for this experiment" html-for="form-desc">
102
- <textarea
103
- id="form-desc"
104
- placeholder="Describe the experiment..."
105
- rows="3"
106
- style="width: 100%; padding: 0.5rem 0.75rem; border: 1px solid var(--border-color, #e2e8f0); border-radius: 0.375rem; font-size: 0.875rem; background: var(--bg-primary, #fff); color: var(--text-primary, #1e293b); resize: vertical; box-sizing: border-box;"
107
- />
72
+ <BaseInput id="form-desc" type="textarea" placeholder="Describe the experiment..." :rows="3" />
108
73
  </FormField>
109
74
  <FormField label="Plate Count" :required="true" error="Must be between 1 and 50" html-for="form-count">
110
- <input
111
- id="form-count"
112
- type="number"
113
- value="100"
114
- style="width: 100%; padding: 0.5rem 0.75rem; border: 1px solid #ef4444; border-radius: 0.375rem; font-size: 0.875rem; background: var(--bg-primary, #fff); color: var(--text-primary, #1e293b); box-sizing: border-box;"
115
- />
75
+ <BaseInput id="form-count" inputmode="numeric" model-value="100" invalid />
116
76
  </FormField>
117
77
  </div>
118
78
  </Variant>
@@ -29,6 +29,7 @@ import LoadingSpinner from './LoadingSpinner.vue'
29
29
  import PluginWorkspaceView from './PluginWorkspaceView.vue'
30
30
  import { GENERATED_JOBS_API_KEY } from './internal/generatedJobsApi'
31
31
  import { definePluginWorkspaceView } from '../composables/componentSpecs'
32
+ import { providePluginIdentity } from '../composables/pluginIdentity'
32
33
 
33
34
  interface Props {
34
35
  baseUrl: string
@@ -74,7 +75,7 @@ const forms = computed<Record<string, FormSchema>>(() => Object.fromEntries(
74
75
 
75
76
  const workspace = computed(() => definePluginWorkspaceView({
76
77
  title: generated.manifest.value?.ui.title ?? 'Analysis',
77
- subtitle: generated.manifest.value?.ui.description,
78
+ subtitle: generated.manifest.value?.ui.subtitle || generated.manifest.value?.ui.description,
78
79
  // The platform shell renders the toasts around this view.
79
80
  toasts: false,
80
81
  topBar: {
@@ -85,6 +86,11 @@ const workspace = computed(() => definePluginWorkspaceView({
85
86
  sidebar: { title: 'Parameters', panels: panels.value, forms: forms.value },
86
87
  }))
87
88
 
89
+ // Seeds the standalone top bar icon; the title and subtitle above win over the seeded text.
90
+ watch(() => generated.manifest.value?.ui, (ui) => {
91
+ if (ui) providePluginIdentity({ plugin: { name: ui.title, subtitle: ui.subtitle, description: ui.description, icon: ui.icon } })
92
+ }, { immediate: true })
93
+
88
94
  const currentValues = computed(() => valuesByDefinition[activeDefinitionId.value] ?? {})
89
95
  const currentJob = computed(() => {
90
96
  const currentSessionId = generated.session.value?.id
@@ -15,7 +15,7 @@ const props = withDefaults(defineProps<Props>(), {
15
15
  })
16
16
 
17
17
  const stateConfig: Record<InstrumentBadgeState, { label: string; tone: string }> = {
18
- running: { label: 'Running', tone: 'info' },
18
+ running: { label: 'Running', tone: 'success' },
19
19
  standby: { label: 'Standby', tone: 'warning' },
20
20
  idle: { label: 'Idle', tone: 'muted' },
21
21
  error: { label: 'Error', tone: 'error' },
@@ -218,7 +218,7 @@ async function clearFinished(): Promise<void> {
218
218
  <header class="mint-jobs-tray__header">
219
219
  <div>
220
220
  <h2 :id="titleId" class="mint-jobs-tray__title">{{ title }}</h2>
221
- <p class="mint-jobs-tray__subtitle">{{ subtitle }}</p>
221
+ <p class="mint-jobs-tray__subtitle" :title="subtitle">{{ subtitle }}</p>
222
222
  </div>
223
223
  <div class="mint-jobs-tray__header-actions">
224
224
  <button
@@ -40,7 +40,7 @@ withDefaults(defineProps<Props>(), {
40
40
  <path
41
41
  style="opacity: 0.75"
42
42
  fill="currentColor"
43
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
43
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
44
44
  />
45
45
  </svg>
46
46
  <span class="mint-spinner__label">{{ label }}</span>
@@ -10,6 +10,7 @@
10
10
  * ```
11
11
  */
12
12
  import { computed, onBeforeUnmount, useAttrs, type StyleValue } from 'vue'
13
+ import { ChevronDown, ChevronUp } from 'lucide-vue-next'
13
14
  import type { UnitOption } from '../types'
14
15
  import NumberUnitInputInternal from './internal/NumberUnitInputInternal.vue'
15
16
 
@@ -287,9 +288,7 @@ function handleScrubStart(event: PointerEvent) {
287
288
  class="mint-number-input__stepper mint-number-input__stepper--down"
288
289
  @click="decrement"
289
290
  >
290
- <svg viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round">
291
- <path d="M2 5h6" />
292
- </svg>
291
+ <ChevronDown :size="12" :stroke-width="2.25" />
293
292
  </button>
294
293
  <button
295
294
  type="button"
@@ -299,9 +298,7 @@ function handleScrubStart(event: PointerEvent) {
299
298
  class="mint-number-input__stepper mint-number-input__stepper--up"
300
299
  @click="increment"
301
300
  >
302
- <svg viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round">
303
- <path d="M5 2v6" /><path d="M2 5h6" />
304
- </svg>
301
+ <ChevronUp :size="12" :stroke-width="2.25" />
305
302
  </button>
306
303
  </div>
307
304
  </div>
@@ -52,8 +52,8 @@ function initState() {
52
52
  <HstText v-model="state.icon" title="icon" />
53
53
  <HstSelect v-model="state.size" title="size" :options="['sm', 'md', 'topbar', 'lg']" />
54
54
  <HstSelect v-model="state.variant" title="variant" :options="['solid', 'tinted']" />
55
- <HstSelect v-model="state.tone" title="tone" :options="['', 'neutral', 'primary', 'cta', 'success', 'warning', 'error', 'info']" />
56
- <HstText v-model="state.color" title="color (CSS color)" />
55
+ <HstSelect v-model="state.tone" title="tone (deprecated, no effect; removed in 1.5)" :options="['', 'neutral', 'primary', 'cta', 'success', 'warning', 'error', 'info']" />
56
+ <HstText v-model="state.color" title="color (deprecated, no effect; removed in 1.5)" />
57
57
  </template>
58
58
  </Variant>
59
59
 
@@ -94,13 +94,5 @@ function initState() {
94
94
  <PluginIcon :icon="SAMPLE_STRUCTURED" size="lg" />
95
95
  </div>
96
96
  </Variant>
97
-
98
- <Variant title="Tone overrides">
99
- <div style="padding: 2rem; display: flex; gap: 1rem; align-items: center;">
100
- <PluginIcon :icon="SAMPLE_PATH" size="lg" variant="solid" color="#0EA5E9" />
101
- <PluginIcon :icon="SAMPLE_PATH" size="lg" variant="solid" color="#F97316" />
102
- <PluginIcon :icon="SAMPLE_PATH" size="lg" variant="solid" color="#22C55E" />
103
- </div>
104
- </Variant>
105
97
  </Story>
106
98
  </template>
@@ -18,9 +18,9 @@ interface Props {
18
18
  icon?: string
19
19
  size?: 'sm' | 'md' | 'topbar' | 'lg'
20
20
  variant?: 'solid' | 'tinted'
21
- /** Semantic color; without it the chip uses the primary color. */
21
+ /** @deprecated v2 design: the tile is always the neutral accent-soft tile, so the tone has no visible effect. Removed in 1.5. */
22
22
  tone?: Tone
23
- /** Custom chip color (any CSS color, such as a plugin's manifest color); replaces the tone. */
23
+ /** @deprecated v2 design: the tile is always the neutral accent-soft tile, so the color has no visible effect. Removed in 1.5. */
24
24
  color?: string
25
25
  }
26
26
 
@@ -274,7 +274,7 @@ async function saveDoseDesign() {
274
274
  </PluginWorkspaceView>
275
275
  <p
276
276
  v-if="lastExperimentEvent"
277
- style="padding: 0.75rem 1rem; margin: 0; font-family: 'Fira Code', monospace; font-size: 0.75rem; color: var(--text-muted);"
277
+ style="padding: 0.75rem 1rem; margin: 0; font-family: var(--font-mono); font-size: 0.75rem; color: var(--text-muted);"
278
278
  >
279
279
  Last shell event: {{ lastExperimentEvent }}
280
280
  </p>
@@ -41,7 +41,7 @@ const emit = defineEmits<{
41
41
 
42
42
  .mint-runtime-update button {
43
43
  padding: 5px 9px;
44
- color: #fff;
44
+ color: var(--text-on-accent);
45
45
  background: var(--color-primary);
46
46
  border: 0;
47
47
  border-radius: 6px;
@@ -135,7 +135,7 @@ function handleSmartGroup(result: AutoGroupResult, state: { groups: SampleGroup[
135
135
  </div>
136
136
  </Variant>
137
137
 
138
- <Variant title="Narrow Action Bar">
138
+ <Variant title="Narrow Header">
139
139
  <div style="padding: 2rem; width: 320px;">
140
140
  <SampleSelector
141
141
  :model-value="['Control_Rep1']"