@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
@@ -18,48 +18,92 @@ const EXPERIMENT_NAMES = [
18
18
  ]
19
19
 
20
20
  const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'processing']
21
+
22
+ const LONG_NAME = 'Glucose tracing in HeLa cells under hypoxia with 13C6-glucose pulse-chase, 48 h timecourse'
23
+ const TWO_HOURS_AGO = new Date(Date.now() - 2 * 3_600_000).toISOString()
24
+
25
+ function initPlayground() {
26
+ return {
27
+ experimentName: EXPERIMENT_NAMES[0],
28
+ experimentCode: 'EXP-042',
29
+ experimentStatus: 'ongoing',
30
+ experimentId: 42,
31
+ experimentProject: 'Dose-response atlas',
32
+ experimentOwner: 'Jane Doe',
33
+ experimentUpdatedAt: TWO_HOURS_AGO,
34
+ isResolving: false,
35
+ error: '',
36
+ showSave: true,
37
+ showDetach: true,
38
+ saveDisabled: false,
39
+ saveDisabledMessage: '',
40
+ saveLoading: false,
41
+ showSuccess: false,
42
+ saveSuccessMessage: 'Saved',
43
+ saveLabel: 'Save',
44
+ dirty: false,
45
+ confirmSave: true,
46
+ placeholder: 'Attach experiment',
47
+ maxNameWidth: 200,
48
+ }
49
+ }
21
50
  </script>
22
51
 
23
52
  <template>
24
53
  <Story title="Experiment/ExperimentPopover">
25
54
  <!-- Playground — all props wired up -->
26
- <Variant title="Playground">
55
+ <Variant title="Playground" :init-state="initPlayground">
27
56
  <template #default="{ state }">
28
57
  <div style="padding: 3rem; display: flex; align-items: center; justify-content: center;">
29
58
  <ExperimentPopover
30
59
  :experiment-name="state.experimentName"
31
60
  :experiment-code="state.experimentCode"
32
61
  :experiment-status="state.experimentStatus"
62
+ :experiment-id="state.experimentId"
63
+ :experiment-project="state.experimentProject"
64
+ :experiment-owner="state.experimentOwner"
65
+ :experiment-updated-at="state.experimentUpdatedAt"
33
66
  :is-resolving="state.isResolving"
34
- :error="state.error"
67
+ :error="state.error || undefined"
35
68
  :show-save="state.showSave"
36
69
  :show-detach="state.showDetach"
37
70
  :save-disabled="state.saveDisabled"
38
71
  :save-loading="state.saveLoading"
39
72
  :save-success-message="state.showSuccess ? state.saveSuccessMessage : ''"
40
73
  :save-disabled-message="state.saveDisabledMessage"
74
+ :save-label="state.saveLabel"
75
+ :dirty="state.dirty"
41
76
  :confirm-save="state.confirmSave"
77
+ :placeholder="state.placeholder"
78
+ :max-name-width="state.maxNameWidth"
42
79
  @select="logAction('select')"
43
80
  @save="logAction('save')"
44
81
  @detach="logAction('detach')"
45
82
  />
46
83
  </div>
47
- <div v-if="lastAction" style="text-align: center; font-family: 'Fira Code', monospace; font-size: 0.75rem; color: var(--text-muted);">
84
+ <div v-if="lastAction" style="text-align: center; font-family: var(--font-mono); font-size: 0.75rem; color: var(--text-muted);">
48
85
  Last event: <code>{{ lastAction }}</code>
49
86
  </div>
50
87
  </template>
51
88
 
52
89
  <template #controls="{ state }">
53
- <HstText v-model="state.experimentCode" title="Experiment code (shown in panel)" />
54
- <HstText v-model="state.experimentName" title="Experiment name (shown in trigger)" />
90
+ <HstText v-model="state.experimentCode" title="Experiment code" />
91
+ <HstText v-model="state.experimentName" title="Experiment name" />
92
+ <HstNumber v-model="state.maxNameWidth" title="Max name width (px)" />
55
93
  <HstSelect
56
94
  v-model="state.experimentStatus"
57
95
  title="Status"
58
96
  :options="STATUSES.map(s => ({ value: s, label: s }))"
59
97
  />
98
+ <HstText v-model="state.experimentProject" title="Project" />
99
+ <HstText v-model="state.experimentOwner" title="Owner" />
100
+ <HstNumber v-model="state.experimentId" title="Experiment id (Open in MINT)" />
60
101
  <HstCheckbox v-model="state.isResolving" title="Resolving (id bound, name pending)" />
61
102
  <HstText v-model="state.error" title="Resolution error message" />
62
- <HstCheckbox v-model="state.showSave" title="Show save (split mode)" />
103
+ <HstText v-model="state.placeholder" title="Empty-state placeholder" />
104
+ <HstCheckbox v-model="state.showSave" title="Show save (joined group)" />
105
+ <HstText v-model="state.saveLabel" title="Save label" />
106
+ <HstCheckbox v-model="state.dirty" title="Dirty (unsaved changes)" />
63
107
  <HstCheckbox v-model="state.showDetach" title="Show detach action" />
64
108
  <HstCheckbox v-model="state.saveDisabled" title="Save disabled" />
65
109
  <HstText v-model="state.saveDisabledMessage" title="Save-disabled tooltip" />
@@ -79,7 +123,7 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
79
123
  <ExperimentPopover />
80
124
  </div>
81
125
  <div style="text-align: center; font-size: 0.75rem; color: var(--text-muted); padding: 0 1rem 1rem;">
82
- Trigger reads "No experiment" in <code>--text-muted</code>. Click to open panel → "Select Experiment" CTA.
126
+ Dashed trigger with a link icon reads "Attach experiment". One click emits <code>select</code>; there is no panel.
83
127
  </div>
84
128
  </Variant>
85
129
 
@@ -94,7 +138,7 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
94
138
  <div style="text-align: center; font-size: 0.75rem; color: var(--text-muted); padding: 0 2rem 1rem;">
95
139
  A deep link bound an experiment id and the platform has not returned the record yet. The chip shows a
96
140
  placeholder bar — never "No experiment", and never the numeric id, because a name may only come from the
97
- platform. Open the panel to see the matching skeleton card.
141
+ platform. Open the panel to see the matching skeleton header.
98
142
  </div>
99
143
  </Variant>
100
144
 
@@ -109,7 +153,7 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
109
153
  <div style="text-align: center; font-size: 0.75rem; color: var(--text-muted); padding: 0 2rem 1rem;">
110
154
  The bound experiment could not be loaded — deleted, or the user lost access. The chip reads
111
155
  "Experiment unavailable" in the error palette; the panel carries the platform's message and keeps
112
- <strong>Change</strong> and <strong>Detach</strong> reachable so the user is never stranded.
156
+ <strong>Change experiment…</strong> and <strong>Detach</strong> reachable so the user is never stranded.
113
157
  </div>
114
158
  </Variant>
115
159
 
@@ -123,17 +167,22 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
123
167
  experiment-name="DRP Dose-Response Screen · March 2026"
124
168
  experiment-code="EXP-042"
125
169
  experiment-status="ongoing"
170
+ :experiment-id="42"
171
+ experiment-project="Dose-response atlas"
172
+ experiment-owner="Jane Doe"
173
+ :experiment-updated-at="TWO_HOURS_AGO"
126
174
  show-detach
127
175
  />
128
176
  </div>
129
177
  <div style="text-align: center; font-size: 0.75rem; color: var(--text-muted); padding: 0 1rem 1rem;">
130
- Trigger shows the name. Click to open the panel — formal code, status, and actions live inside.
178
+ Trigger shows the code badge and the name. Click to open the panel: Project / Status / Owner / Updated, then
179
+ the Change, Open in MINT and Detach rows.
131
180
  </div>
132
181
  </Variant>
133
182
 
134
- <!-- With save — the split button pair -->
183
+ <!-- With save — the joined group -->
135
184
  <Variant
136
- title="With save (split)"
185
+ title="With save (joined)"
137
186
  :init-state="() => ({})"
138
187
  >
139
188
  <div style="padding: 3rem; display: flex; align-items: center; justify-content: center;">
@@ -148,8 +197,8 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
148
197
  />
149
198
  </div>
150
199
  <div style="text-align: center; font-size: 0.75rem; color: var(--text-muted); padding: 0 1rem 1rem;">
151
- Attached [chip | save] unit. Hover on either half lifts both together; each half keeps its own color-hover to
152
- signal which action is "hot".
200
+ One bordered group <code>[ code name ▾ | Save ]</code>. The trigger drops its own border; Save sits behind a
201
+ 1px divider with the accent fill. Flat: hover changes colour only.
153
202
  </div>
154
203
  </Variant>
155
204
 
@@ -169,6 +218,55 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
169
218
  </div>
170
219
  </Variant>
171
220
 
221
+ <!-- Unsaved changes -->
222
+ <Variant
223
+ title="Save — dirty"
224
+ :init-state="() => ({})"
225
+ >
226
+ <div style="padding: 3rem; display: flex; align-items: center; justify-content: center;">
227
+ <ExperimentPopover
228
+ experiment-name="Kinase Inhibitor Panel — Plate 04"
229
+ experiment-code="EXP-204"
230
+ experiment-status="ongoing"
231
+ show-save
232
+ dirty
233
+ :confirm-save="false"
234
+ @save="logAction('save dirty')"
235
+ />
236
+ </div>
237
+ <div style="text-align: center; font-size: 0.75rem; color: var(--text-muted); padding: 0 1rem 1rem;">
238
+ A 6px dot with a 1.5px accent ring marks unsaved changes. It hides while saving.
239
+ </div>
240
+ </Variant>
241
+
242
+ <!-- Long name truncated at maxNameWidth -->
243
+ <Variant
244
+ title="Long name"
245
+ :init-state="() => ({})"
246
+ >
247
+ <div style="padding: 3rem; display: flex; flex-direction: column; align-items: center; gap: 1rem;">
248
+ <ExperimentPopover
249
+ :experiment-name="LONG_NAME"
250
+ experiment-code="EXP-0159"
251
+ experiment-status="ongoing"
252
+ :experiment-id="159"
253
+ show-save
254
+ show-detach
255
+ />
256
+ <ExperimentPopover
257
+ :experiment-name="LONG_NAME"
258
+ experiment-code="EXP-0159"
259
+ experiment-status="ongoing"
260
+ :max-name-width="120"
261
+ show-save
262
+ />
263
+ </div>
264
+ <div style="text-align: center; font-size: 0.75rem; color: var(--text-muted); padding: 0 1rem 1rem;">
265
+ The name truncates at <code>maxNameWidth</code> (200px default, 120px below). Hover shows
266
+ "code · name"; the panel header wraps the full name.
267
+ </div>
268
+ </Variant>
269
+
172
270
  <!-- Save success state -->
173
271
  <Variant
174
272
  title="Save — success"
@@ -206,8 +304,7 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
206
304
  />
207
305
  </div>
208
306
  <p style="text-align: center; font-size: 0.75rem; color: var(--text-muted); padding: 0 2rem 1rem;">
209
- Click the save half to open the <code>ConfirmDialog</code> — shows the refresh-aligned info variant with the
210
- primary-blue icon, refined 15px title, and the full tactile-button system on both Cancel and Save.
307
+ Click Save to open the info <code>ConfirmDialog</code>. <code>confirmSave</code> defaults to true.
211
308
  </p>
212
309
  </Variant>
213
310
 
@@ -227,7 +324,7 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
227
324
  />
228
325
  </div>
229
326
  <div style="text-align: center; font-size: 0.75rem; color: var(--text-muted); padding: 0 1rem 1rem;">
230
- Hover the save half to see the tooltip explaining why it's disabled.
327
+ Hover Save to see the tooltip explaining why it's disabled.
231
328
  </div>
232
329
  </Variant>
233
330
 
@@ -244,8 +341,8 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
244
341
  />
245
342
  </div>
246
343
  <div style="text-align: center; font-size: 0.75rem; color: var(--text-muted); padding: 0 1rem 1rem;">
247
- When no <code>experimentCode</code> is provided (older data), the trigger falls back to showing the name
248
- so the UI never reads as "unknown".
344
+ When no <code>experimentCode</code> is provided (older data), the trigger shows the name alone, so the UI
345
+ never reads as "unknown".
249
346
  </div>
250
347
  </Variant>
251
348
 
@@ -264,7 +361,6 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
264
361
  background: var(--bg-card);
265
362
  border: 1px solid var(--border-light);
266
363
  border-radius: var(--radius-lg);
267
- box-shadow: var(--shadow-sm);
268
364
  "
269
365
  >
270
366
  <!-- Mock: brand + page selector + plugin chip (visual only) -->
@@ -272,12 +368,12 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
272
368
  style="
273
369
  width: 1.625rem;
274
370
  height: 1.625rem;
275
- background: linear-gradient(135deg, var(--color-primary-hover) 0%, var(--color-primary) 100%);
276
- border-radius: 0.375rem;
371
+ background: var(--color-primary);
372
+ border-radius: var(--radius-md);
277
373
  display: grid;
278
374
  place-items: center;
279
- color: white;
280
- font-family: 'Fira Code', monospace;
375
+ color: var(--text-on-accent);
376
+ font-family: var(--font-mono);
281
377
  font-weight: 700;
282
378
  font-size: 0.6875rem;
283
379
  "
@@ -302,10 +398,10 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
302
398
  style="
303
399
  width: 1.875rem;
304
400
  height: 1.875rem;
305
- background: linear-gradient(135deg, var(--color-primary-hover) 0%, var(--color-primary) 100%);
401
+ background: var(--bg-tertiary);
306
402
  border: 1px solid var(--border-color);
307
403
  border-radius: 9999px;
308
- color: white;
404
+ color: var(--text-secondary);
309
405
  display: grid;
310
406
  place-items: center;
311
407
  font-size: 0.6875rem;
@@ -316,7 +412,7 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
316
412
  </div>
317
413
  </div>
318
414
  <p style="margin-top: 1rem; font-size: 0.75rem; color: var(--text-muted); text-align: center;">
319
- Same 2.375rem height and refresh-design motion as the AppTopBar page selector.
415
+ The joined group sits in the top bar's right cluster. The panel opens below, aligned to the group's left edge; near the viewport's right edge it aligns to the group's right edge.
320
416
  </p>
321
417
  </div>
322
418
  </Variant>
@@ -329,15 +425,15 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
329
425
  <div style="padding: 2rem; display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem;">
330
426
  <div style="display: flex; flex-direction: column; align-items: center; gap: 0.625rem;">
331
427
  <ExperimentPopover />
332
- <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: 'Fira Code', monospace;">empty</span>
428
+ <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: var(--font-mono);">empty</span>
333
429
  </div>
334
430
  <div style="display: flex; flex-direction: column; align-items: center; gap: 0.625rem;">
335
431
  <ExperimentPopover is-resolving show-detach />
336
- <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: 'Fira Code', monospace;">resolving</span>
432
+ <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: var(--font-mono);">resolving</span>
337
433
  </div>
338
434
  <div style="display: flex; flex-direction: column; align-items: center; gap: 0.625rem;">
339
435
  <ExperimentPopover error="Experiment not found (404)" show-detach />
340
- <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: 'Fira Code', monospace;">resolve failed</span>
436
+ <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: var(--font-mono);">resolve failed</span>
341
437
  </div>
342
438
  <div style="display: flex; flex-direction: column; align-items: center; gap: 0.625rem;">
343
439
  <ExperimentPopover
@@ -346,7 +442,7 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
346
442
  experiment-status="ongoing"
347
443
  show-detach
348
444
  />
349
- <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: 'Fira Code', monospace;">selected</span>
445
+ <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: var(--font-mono);">selected</span>
350
446
  </div>
351
447
  <div style="display: flex; flex-direction: column; align-items: center; gap: 0.625rem;">
352
448
  <ExperimentPopover
@@ -357,7 +453,7 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
357
453
  show-detach
358
454
  :confirm-save="false"
359
455
  />
360
- <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: 'Fira Code', monospace;">selected + save</span>
456
+ <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: var(--font-mono);">selected + save</span>
361
457
  </div>
362
458
  <div style="display: flex; flex-direction: column; align-items: center; gap: 0.625rem;">
363
459
  <ExperimentPopover
@@ -367,7 +463,7 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
367
463
  show-save
368
464
  save-loading
369
465
  />
370
- <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: 'Fira Code', monospace;">save loading</span>
466
+ <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: var(--font-mono);">save loading</span>
371
467
  </div>
372
468
  <div style="display: flex; flex-direction: column; align-items: center; gap: 0.625rem;">
373
469
  <ExperimentPopover
@@ -377,7 +473,21 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
377
473
  show-save
378
474
  save-success-message="Saved"
379
475
  />
380
- <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: 'Fira Code', monospace;">save success</span>
476
+ <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: var(--font-mono);">save success</span>
477
+ </div>
478
+ <div style="display: flex; flex-direction: column; align-items: center; gap: 0.625rem;">
479
+ <ExperimentPopover
480
+ :experiment-name="EXPERIMENT_NAMES[2]"
481
+ experiment-code="EXP-204"
482
+ experiment-status="ongoing"
483
+ show-save
484
+ dirty
485
+ />
486
+ <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: var(--font-mono);">save dirty</span>
487
+ </div>
488
+ <div style="display: flex; flex-direction: column; align-items: center; gap: 0.625rem;">
489
+ <ExperimentPopover :experiment-name="LONG_NAME" experiment-code="EXP-0159" show-save />
490
+ <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: var(--font-mono);">long name</span>
381
491
  </div>
382
492
  <div style="display: flex; flex-direction: column; align-items: center; gap: 0.625rem;">
383
493
  <ExperimentPopover
@@ -388,7 +498,7 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
388
498
  save-disabled
389
499
  save-disabled-message="No data yet"
390
500
  />
391
- <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: 'Fira Code', monospace;">save disabled</span>
501
+ <span style="font-size: 0.6875rem; color: var(--text-muted); font-family: var(--font-mono);">save disabled</span>
392
502
  </div>
393
503
  </div>
394
504
  </Variant>
@@ -398,22 +508,24 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
398
508
  <docs lang="md">
399
509
  ## ExperimentPopover
400
510
 
401
- The experiment context chip that lives in `AppTopBar`'s right cluster. Shows the
402
- currently linked experiment, opens a popover with Change / Detach actions, and
403
- optionally pairs with an inline Save button as a split unit.
511
+ The experiment control in `AppTopBar`'s right cluster. Shows the attached
512
+ experiment (code badge + name), opens a 300px summary panel, and optionally joins
513
+ a Save button into one bordered group: `[ EXP-0159 Glucose tracing… ▾ | Save ]`.
404
514
 
405
- ### Refresh-design alignment
515
+ ### Design v2
406
516
 
407
- - **Height** `2.375rem` — matches the `AppTopBar page selector` so the
408
- topbar's right cluster reads at one optical baseline.
409
- - **Surface** `--bg-card` + `--border-color` — consistent with the white-card +
410
- border language.
411
- - **Hover** `translateY(-1px) + var(--shadow-sm)`, gated behind `:not(--active)`
412
- so the chip stays put once its panel opens.
413
- - **Active** border-primary + `0 0 0 3px var(--color-primary-soft)` ring, same
414
- treatment as the other refresh triggers.
415
- - **Split cohesion** — when save is visible, the `[chip | save]` pair lifts
416
- together on either-half hover while keeping per-half color feedback.
517
+ - **Trigger** 30px, borderless; hover and open fill `--bg-hover`. The name
518
+ truncates at `maxNameWidth` (default 200px); `title` is "code · name".
519
+ - **Joined group** (`showSave`) 1px `--border-color`, `--radius-md`,
520
+ `--bg-card`. Save: `--color-primary` fill, `--text-on-accent` text, 13px/600,
521
+ cloud-upload icon, 1px left divider. `saveLoading` shows a spinner and
522
+ "Saving…" and disables the button; `dirty` adds a ringed dot.
523
+ - **Panel** code badge, name (wraps), Project / Status / Owner / Updated, a
524
+ separator, then three menu rows: Change experiment…, Open in MINT (new tab to
525
+ `/experiments/{experimentId}`), Detach (danger, `showDetach` only). No buttons.
526
+ - **Empty** dashed trigger with a link icon and `placeholder`; one click emits
527
+ `select`.
528
+ - Flat: no lift, no glow, no coloured shadow.
417
529
 
418
530
  ### Binding states
419
531
 
@@ -424,11 +536,11 @@ The chip renders four mutually exclusive states, in this order:
424
536
  | Resolving | `isResolving` | placeholder bar (`aria-busy`) |
425
537
  | Failed | `error` | "Experiment unavailable" |
426
538
  | Linked | `experimentName` / `experimentCode` | the platform's name, code as fallback |
427
- | Empty | none of the above | "No experiment" |
539
+ | Empty | none of the above | `placeholder` ("Attach experiment") |
428
540
 
429
541
  Resolving and failed exist because an experiment can be *bound by id* before its
430
542
  record arrives — a deep link, or a restored session. In those states the chip must
431
- not read "No experiment", and it must never substitute the numeric id for a name:
543
+ not read as empty, and it must never substitute the numeric id for a name:
432
544
  **a displayed name only ever comes from the platform.** Detach stays reachable in
433
545
  the failed state so a user is never stranded on an experiment that cannot load.
434
546
 
@@ -436,7 +548,7 @@ the failed state so a user is never stranded on an experiment that cannot load.
436
548
 
437
549
  | Event | When |
438
550
  |---|---|
439
- | `select` | User clicked **Select / Change** → parent opens `ExperimentSelectorModal`. |
440
- | `save` | User clicked save (or confirmed the inline ConfirmDialog). |
551
+ | `select` | User clicked the empty trigger or **Change experiment…** → parent opens `ExperimentSelectorModal`. |
552
+ | `save` | User clicked Save (or confirmed the ConfirmDialog, on by default via `confirmSave`). |
441
553
  | `detach` | User clicked detach. |
442
554
  </docs>