@morscherlab/mint-sdk 1.1.25 → 1.2.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 (403) hide show
  1. package/README.md +44 -12
  2. package/dist/{ExperimentPopover-tLwJ7XF4.js → ExperimentPopover-oL0fg_A0.js} +6 -4
  3. package/dist/ExperimentPopover-oL0fg_A0.js.map +1 -0
  4. package/dist/{ExperimentSelectorModal-B6P2msT4.js → ExperimentSelectorModal-BeEOomw_.js} +5 -5
  5. package/dist/ExperimentSelectorModal-BeEOomw_.js.map +1 -0
  6. package/dist/{SettingsModal-BW3Ar-uy.js → SettingsModal-Dphh3RrI.js} +5 -8
  7. package/dist/SettingsModal-Dphh3RrI.js.map +1 -0
  8. package/dist/{EmptyState-p90quq8Z.js → Skeleton-DxtwJ5gt.js} +111 -93
  9. package/dist/Skeleton-DxtwJ5gt.js.map +1 -0
  10. package/dist/__tests__/components/ExperimentTimeline.test.d.ts +1 -0
  11. package/dist/__tests__/components/PluginWorkspaceView.internal.test.d.ts +1 -0
  12. package/dist/__tests__/components/SecretInput.test.d.ts +1 -0
  13. package/dist/__tests__/components/WellEditPopupInternal.test.d.ts +1 -0
  14. package/dist/__tests__/composables/useEventStream.test.d.ts +1 -0
  15. package/dist/__tests__/composables/useListReorder.test.d.ts +1 -0
  16. package/dist/__tests__/styles/cssTokens.test.d.ts +1 -0
  17. package/dist/components/ActionMenu.vue.d.ts +1 -1
  18. package/dist/components/AppAvatarMenu.vue.d.ts +4 -0
  19. package/dist/components/AppPluginSwitcher.vue.d.ts +2 -0
  20. package/dist/components/AppSidebar.vue.d.ts +1 -1
  21. package/dist/components/AuditTrail.vue.d.ts +13 -0
  22. package/dist/components/BatchProgressList.vue.d.ts +17 -0
  23. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +1 -1
  24. package/dist/components/DataFrame.vue.d.ts +14 -2
  25. package/dist/components/DatePicker.vue.d.ts +1 -1
  26. package/dist/components/DateTimePicker.vue.d.ts +2 -0
  27. package/dist/components/DropdownButton.vue.d.ts +2 -0
  28. package/dist/components/ExperimentDataViewer.vue.d.ts +26 -1
  29. package/dist/components/ExperimentPopover.vue.d.ts +1 -0
  30. package/dist/components/ExperimentTimeline.vue.d.ts +29 -1
  31. package/dist/components/FitPanel.vue.d.ts +1 -1
  32. package/dist/components/LcmsSequenceTable.vue.d.ts +29 -1
  33. package/dist/components/PluginWorkspaceView.props.d.ts +29 -1
  34. package/dist/components/PluginWorkspaceView.vue.d.ts +5 -5
  35. package/dist/components/ProgressBar.vue.d.ts +1 -1
  36. package/dist/components/ProtocolStepEditor.vue.d.ts +1 -0
  37. package/dist/components/ReagentList.vue.d.ts +29 -1
  38. package/dist/components/SampleHierarchyTree.vue.d.ts +29 -1
  39. package/dist/components/SampleSelector.drag.d.ts +13 -0
  40. package/dist/components/SampleSelector.tree.d.ts +2 -0
  41. package/dist/components/SampleSelector.vue.d.ts +34 -1
  42. package/dist/components/ScheduleCalendar.vue.d.ts +20 -0
  43. package/dist/components/SecretInput.vue.d.ts +28 -0
  44. package/dist/components/SmartGroupFieldRecipe.vue.d.ts +2 -1
  45. package/dist/components/TimePicker.vue.d.ts +1 -0
  46. package/dist/components/WellPlate.geometry.d.ts +15 -0
  47. package/dist/components/WellPlate.interaction.d.ts +13 -0
  48. package/dist/components/WellPlate.vue.d.ts +29 -0
  49. package/dist/components/index.d.ts +9 -0
  50. package/dist/components/index.js +8 -8
  51. package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -0
  52. package/dist/components/internal/DataStateInternal.vue.d.ts +43 -0
  53. package/dist/components/internal/WellEditPopupInternal.vue.d.ts +3 -1
  54. package/dist/components/internal/WorkspaceControlsInternal.vue.d.ts +23 -0
  55. package/dist/components/internal/WorkspaceShellInternal.vue.d.ts +70 -0
  56. package/dist/components/internal/WorkspaceSidebarInternal.vue.d.ts +43 -0
  57. package/dist/{components-DQt0tcpY.js → components-CgAUzF8F.js} +2491 -2235
  58. package/dist/components-CgAUzF8F.js.map +1 -0
  59. package/dist/composables/controlSchemaTypes.d.ts +34 -0
  60. package/dist/composables/flattenWorkspaceBindings.d.ts +61 -0
  61. package/dist/composables/index.d.ts +8 -2
  62. package/dist/composables/index.js +10 -10
  63. package/dist/composables/pluginWorkspaceModel.d.ts +164 -0
  64. package/dist/composables/useAsync.d.ts +5 -1
  65. package/dist/composables/useBioTemplateWorkspace.d.ts +4 -20
  66. package/dist/composables/useCommandHistory.d.ts +5 -1
  67. package/dist/composables/useDropdownState.d.ts +18 -2
  68. package/dist/composables/useEventStream.d.ts +52 -0
  69. package/dist/composables/useFocusTrap.d.ts +21 -0
  70. package/dist/composables/useJobsStatusTray.d.ts +1 -1
  71. package/dist/composables/useListReorder.d.ts +90 -0
  72. package/dist/composables/useMenuKeyboard.d.ts +29 -0
  73. package/dist/composables/useOptimisticMutation.d.ts +5 -1
  74. package/dist/composables/usePluginClient.d.ts +4 -17
  75. package/dist/composables/usePluginConfig.d.ts +3 -1
  76. package/dist/composables/usePluginWorkspace.d.ts +51 -0
  77. package/dist/composables/useRequestSyncState.d.ts +42 -2
  78. package/dist/composables/useResourceCrud.d.ts +5 -1
  79. package/dist/composables/useRovingFocus.d.ts +24 -0
  80. package/dist/composables/useScheduleDrag.d.ts +1 -0
  81. package/dist/composables/useWellPainting.d.ts +5 -1
  82. package/dist/composables/useWellPlateAdapter.d.ts +5 -1
  83. package/dist/composables/useWellPlateValidation.d.ts +5 -1
  84. package/dist/composables/workspaceRecipes.d.ts +188 -0
  85. package/dist/{composables-CkXDe7nm.js → composables-B_pia3hO.js} +105 -254
  86. package/dist/composables-B_pia3hO.js.map +1 -0
  87. package/dist/{useApi-CDBq6z23.js → errors-_W0-LbA6.js} +102 -3
  88. package/dist/errors-_W0-LbA6.js.map +1 -0
  89. package/dist/{experiment-Y9Iz7KYi.js → experiment-Cwl9AYxw.js} +3 -8
  90. package/dist/experiment-Cwl9AYxw.js.map +1 -0
  91. package/dist/{experiment-utils-CLnzPPq5.js → experiment-utils-DV3DrvE_.js} +70 -51
  92. package/dist/experiment-utils-DV3DrvE_.js.map +1 -0
  93. package/dist/index.d.ts +1 -0
  94. package/dist/index.js +15 -15
  95. package/dist/install.js +2 -7
  96. package/dist/install.js.map +1 -1
  97. package/dist/permissions.d.ts +42 -1
  98. package/dist/permissions.js +20 -10
  99. package/dist/permissions.js.map +1 -1
  100. package/dist/stores/index.d.ts +1 -1
  101. package/dist/stores/index.js +3 -3
  102. package/dist/stores/settings.d.ts +31 -1
  103. package/dist/styles.css +11247 -29501
  104. package/dist/templates/builderReadUtils.d.ts +1 -1
  105. package/dist/templates/index.js +2 -4
  106. package/dist/{templates-CovvRZ-9.js → templates-Qq113pyZ.js} +303 -106
  107. package/dist/templates-Qq113pyZ.js.map +1 -0
  108. package/dist/types/form-builder.d.ts +1 -1
  109. package/dist/{useControlSchema-BhoJzWzx.js → useControlSchema-aMZlb4Sc.js} +684 -1216
  110. package/dist/useControlSchema-aMZlb4Sc.js.map +1 -0
  111. package/dist/{useDropdownState-DSb3BC3N.js → useDropdownState-DitCtrTT.js} +22 -6
  112. package/dist/useDropdownState-DitCtrTT.js.map +1 -0
  113. package/dist/{useExperimentSelector-D0pjj9WH.js → useExperimentSelector-I8HC8hUu.js} +4 -4
  114. package/dist/{useExperimentSelector-D0pjj9WH.js.map → useExperimentSelector-I8HC8hUu.js.map} +1 -1
  115. package/dist/{useFormBuilder-DiEcDis2.js → useFormBuilder-TCxNnqeb.js} +2 -2
  116. package/dist/{useFormBuilder-DiEcDis2.js.map → useFormBuilder-TCxNnqeb.js.map} +1 -1
  117. package/dist/{useProtocolTemplates-DHwSveE6.js → useProtocolTemplates-BmX_iVBR.js} +660 -197
  118. package/dist/useProtocolTemplates-BmX_iVBR.js.map +1 -0
  119. package/dist/utils/color.d.ts +13 -0
  120. package/dist/utils/errors.d.ts +9 -0
  121. package/dist/utils/pluginSecrets.d.ts +53 -0
  122. package/dist/utils/records.d.ts +4 -0
  123. package/package.json +1 -1
  124. package/src/__tests__/components/ActionMenu.test.ts +94 -0
  125. package/src/__tests__/components/AppAvatarMenu.test.ts +123 -0
  126. package/src/__tests__/components/BaseTabs.test.ts +67 -0
  127. package/src/__tests__/components/DataFrame.test.ts +183 -11
  128. package/src/__tests__/components/DataStates.test.ts +176 -0
  129. package/src/__tests__/components/DatePicker.test.ts +76 -0
  130. package/src/__tests__/components/DateTimePicker.test.ts +36 -0
  131. package/src/__tests__/components/DropdownButton.test.ts +134 -0
  132. package/src/__tests__/components/ExperimentTimeline.test.ts +63 -0
  133. package/src/__tests__/components/GeneratedPluginView.test.ts +0 -5
  134. package/src/__tests__/components/GroupAssigner.test.ts +53 -0
  135. package/src/__tests__/components/JobsStatusTray.test.ts +20 -18
  136. package/src/__tests__/components/LcmsSequenceTable.test.ts +22 -0
  137. package/src/__tests__/components/PluginWorkspaceView.internal.test.ts +230 -0
  138. package/src/__tests__/components/RackEditor.test.ts +35 -0
  139. package/src/__tests__/components/ReagentList.test.ts +64 -2
  140. package/src/__tests__/components/RetiredStyles.test.ts +31 -0
  141. package/src/__tests__/components/SampleSelector.drag.test.ts +51 -1
  142. package/src/__tests__/components/ScheduleCalendar.test.ts +63 -0
  143. package/src/__tests__/components/SecretInput.test.ts +87 -0
  144. package/src/__tests__/components/SegmentedControl.test.ts +88 -0
  145. package/src/__tests__/components/TimePicker.test.ts +61 -0
  146. package/src/__tests__/components/WellEditPopupInternal.test.ts +108 -0
  147. package/src/__tests__/components/WellPlate.interaction.test.ts +59 -0
  148. package/src/__tests__/composables/useAuth.test.ts +25 -0
  149. package/src/__tests__/composables/useDropdownState.test.ts +58 -1
  150. package/src/__tests__/composables/useEventStream.test.ts +140 -0
  151. package/src/__tests__/composables/useGeneratedAnalysis.test.ts +103 -0
  152. package/src/__tests__/composables/useJobsStatusTray.test.ts +25 -161
  153. package/src/__tests__/composables/useListReorder.test.ts +145 -0
  154. package/src/__tests__/composables/usePluginClient.test.ts +148 -13
  155. package/src/__tests__/composables/useRequestSyncState.test.ts +140 -0
  156. package/src/__tests__/docs/frontendDocsCatalog.test.ts +55 -2
  157. package/src/__tests__/stores/settings.test.ts +71 -1
  158. package/src/__tests__/styles/cssTokens.test.ts +131 -0
  159. package/src/__tests__/utils/permissions.test.ts +68 -0
  160. package/src/components/ActionMenu.vue +45 -88
  161. package/src/components/AlertBox.vue +0 -4
  162. package/src/components/AppAvatarMenu.vue +26 -7
  163. package/src/components/AppLayout.vue +0 -4
  164. package/src/components/AppPluginSwitcher.story.vue +87 -0
  165. package/src/components/AppPluginSwitcher.vue +2 -5
  166. package/src/components/AppSidebar.vue +0 -4
  167. package/src/components/AppToastContainer.vue +6 -8
  168. package/src/components/AppTopBar.vue +0 -4
  169. package/src/components/ArtifactSaveDialog.vue +0 -4
  170. package/src/components/ArtifactSelectorModal.vue +0 -4
  171. package/src/components/AuditTrail.story.vue +18 -0
  172. package/src/components/AuditTrail.vue +27 -7
  173. package/src/components/AutoGroupModal.vue +0 -4
  174. package/src/components/Avatar.vue +0 -4
  175. package/src/components/BaseButton.vue +0 -4
  176. package/src/components/BaseCheckbox.vue +0 -4
  177. package/src/components/BaseInput.vue +0 -4
  178. package/src/components/BaseModal.vue +8 -60
  179. package/src/components/BasePill.vue +0 -4
  180. package/src/components/BaseRadioGroup.vue +0 -4
  181. package/src/components/BaseSelect.vue +0 -4
  182. package/src/components/BaseSlider.vue +0 -4
  183. package/src/components/BaseTabs.vue +26 -5
  184. package/src/components/BaseTextarea.vue +0 -4
  185. package/src/components/BaseToggle.vue +0 -4
  186. package/src/components/BatchProgressList.story.vue +18 -0
  187. package/src/components/BatchProgressList.vue +32 -4
  188. package/src/components/BioTemplateExperimentWorkspaceView.vue +10 -21
  189. package/src/components/BioTemplatePackWorkspaceView.vue +11 -15
  190. package/src/components/BioTemplatePresetWorkspaceView.vue +8 -13
  191. package/src/components/Breadcrumb.vue +0 -4
  192. package/src/components/Calendar.vue +0 -4
  193. package/src/components/ChartContainer.story.vue +1 -1
  194. package/src/components/ChartContainer.vue +0 -4
  195. package/src/components/ChemicalFormula.vue +0 -4
  196. package/src/components/CollapsibleCard.vue +0 -4
  197. package/src/components/ColorSlider.vue +0 -4
  198. package/src/components/ComponentBindingRenderer.vue +1 -3
  199. package/src/components/ConcentrationInput.vue +0 -4
  200. package/src/components/ConfirmDialog.vue +0 -4
  201. package/src/components/ControlWorkspaceView.vue +11 -22
  202. package/src/components/DataFrame.story.vue +14 -0
  203. package/src/components/DataFrame.vue +97 -23
  204. package/src/components/DatePicker.vue +73 -27
  205. package/src/components/DateTimePicker.vue +45 -17
  206. package/src/components/Divider.vue +0 -4
  207. package/src/components/DoseCalculator.vue +0 -4
  208. package/src/components/DoseDesignWorkspaceView.vue +6 -25
  209. package/src/components/DropdownButton.vue +24 -6
  210. package/src/components/EmptyState.vue +0 -4
  211. package/src/components/ExperimentCodeBadge.vue +0 -4
  212. package/src/components/ExperimentDataViewer.story.vue +3 -0
  213. package/src/components/ExperimentDataViewer.vue +33 -13
  214. package/src/components/ExperimentPopover.vue +2 -5
  215. package/src/components/ExperimentSelectorModal.vue +0 -4
  216. package/src/components/ExperimentTimeline.story.vue +18 -0
  217. package/src/components/ExperimentTimeline.vue +81 -75
  218. package/src/components/FileUploader.vue +0 -4
  219. package/src/components/FitPanel.vue +0 -4
  220. package/src/components/FormActions.story.vue +96 -0
  221. package/src/components/FormActions.vue +0 -4
  222. package/src/components/FormBuilder.vue +0 -4
  223. package/src/components/FormField.vue +0 -4
  224. package/src/components/FormulaInput.vue +0 -4
  225. package/src/components/GeneratedPathInput.story.vue +68 -0
  226. package/src/components/GeneratedPlotlyResult.story.vue +96 -0
  227. package/src/components/GeneratedPlotlyResult.vue +3 -3
  228. package/src/components/GeneratedPluginView.story.vue +145 -0
  229. package/src/components/GeneratedPluginView.vue +10 -10
  230. package/src/components/GeneratedResultRenderer.story.vue +117 -0
  231. package/src/components/GeneratedResultRenderer.vue +4 -4
  232. package/src/components/GroupAssigner.vue +67 -5
  233. package/src/components/IconButton.vue +0 -4
  234. package/src/components/InstrumentAlertLog.vue +0 -4
  235. package/src/components/InstrumentStateBadge.vue +0 -4
  236. package/src/components/InstrumentStatusCard.vue +0 -4
  237. package/src/components/JobsStatusTray.story.vue +6 -10
  238. package/src/components/JobsStatusTray.vue +0 -4
  239. package/src/components/LcmsSequenceTable.vue +81 -50
  240. package/src/components/LoadingSpinner.vue +0 -4
  241. package/src/components/MobileSupportGate.vue +0 -4
  242. package/src/components/MoleculeInput.vue +0 -4
  243. package/src/components/MultiSelect.vue +0 -4
  244. package/src/components/NumberInput.vue +0 -4
  245. package/src/components/PlateMapEditor.vue +3 -14
  246. package/src/components/PluginIcon.vue +0 -4
  247. package/src/components/PluginWorkspaceView.props.ts +38 -0
  248. package/src/components/PluginWorkspaceView.vue +53 -224
  249. package/src/components/ProgressBar.vue +0 -4
  250. package/src/components/ProtocolStepEditor.vue +2 -4
  251. package/src/components/RackEditor.vue +51 -70
  252. package/src/components/ReagentEditor.vue +0 -4
  253. package/src/components/ReagentList.story.vue +18 -0
  254. package/src/components/ReagentList.vue +127 -82
  255. package/src/components/ResourceCard.vue +0 -4
  256. package/src/components/RuntimeUpdateNotice.story.vue +49 -0
  257. package/src/components/RuntimeUpdateNotice.vue +4 -4
  258. package/src/components/SampleHierarchyTree.story.vue +18 -0
  259. package/src/components/SampleHierarchyTree.vue +32 -9
  260. package/src/components/SampleLegend.vue +2 -8
  261. package/src/components/SampleSelector.drag.ts +51 -0
  262. package/src/components/SampleSelector.story.vue +6 -0
  263. package/src/components/SampleSelector.tree.ts +2 -0
  264. package/src/components/SampleSelector.vue +49 -10
  265. package/src/components/SampleSelectorGroupNode.vue +19 -0
  266. package/src/components/ScheduleCalendar.story.vue +6 -0
  267. package/src/components/ScheduleCalendar.vue +82 -7
  268. package/src/components/ScientificNumber.vue +0 -4
  269. package/src/components/SecretInput.story.vue +79 -0
  270. package/src/components/SecretInput.vue +150 -0
  271. package/src/components/SectionCard.vue +0 -4
  272. package/src/components/SegmentedControl.vue +48 -5
  273. package/src/components/SequenceInput.vue +0 -4
  274. package/src/components/SequenceProgressBar.story.vue +110 -0
  275. package/src/components/SequenceProgressBar.vue +0 -4
  276. package/src/components/SettingsModal.vue +0 -4
  277. package/src/components/Skeleton.vue +0 -4
  278. package/src/components/SmartGroupFieldRecipe.vue +8 -6
  279. package/src/components/SmartGroupManual.vue +0 -4
  280. package/src/components/StatusIndicator.vue +0 -4
  281. package/src/components/StepWizard.vue +0 -4
  282. package/src/components/TagsInput.vue +4 -6
  283. package/src/components/ThemeToggle.vue +0 -4
  284. package/src/components/TimePicker.vue +36 -7
  285. package/src/components/TimeRangeInput.vue +0 -4
  286. package/src/components/Tooltip.story.vue +3 -3
  287. package/src/components/Tooltip.vue +0 -4
  288. package/src/components/UnitInput.vue +0 -4
  289. package/src/components/WellPlate.geometry.ts +60 -1
  290. package/src/components/WellPlate.interaction.ts +44 -0
  291. package/src/components/WellPlate.story.vue +8 -0
  292. package/src/components/WellPlate.vue +53 -7
  293. package/src/components/index.ts +9 -0
  294. package/src/components/internal/AppTopBarPageSelectorInternal.vue +2 -5
  295. package/src/components/internal/AppTopBarPillNavInternal.vue +0 -4
  296. package/src/components/internal/DataStateInternal.vue +74 -0
  297. package/src/components/internal/FormSectionRenderer.vue +0 -4
  298. package/src/components/internal/WellEditPopupInternal.vue +20 -5
  299. package/src/components/internal/WorkspaceControlsInternal.vue +20 -0
  300. package/src/components/internal/WorkspaceShellInternal.vue +125 -0
  301. package/src/components/internal/WorkspaceSidebarInternal.vue +82 -0
  302. package/src/composables/controlSchemaTypes.ts +34 -0
  303. package/src/composables/experimentDesignData.ts +1 -3
  304. package/src/composables/flattenWorkspaceBindings.ts +104 -0
  305. package/src/composables/formBuilderRegistry.ts +5 -0
  306. package/src/composables/index.ts +41 -5
  307. package/src/composables/pluginWorkspaceModel.ts +182 -0
  308. package/src/composables/useAsync.ts +5 -1
  309. package/src/composables/useBioTemplatePackWorkspace.ts +15 -9
  310. package/src/composables/useBioTemplatePresetWorkspace.ts +14 -15
  311. package/src/composables/useBioTemplateWorkspace.ts +18 -43
  312. package/src/composables/useCommandHistory.ts +5 -1
  313. package/src/composables/useDropdownState.ts +35 -5
  314. package/src/composables/useEventStream.ts +326 -0
  315. package/src/composables/useFileImport.ts +2 -7
  316. package/src/composables/useFocusTrap.ts +91 -0
  317. package/src/composables/useGeneratedAnalysis.ts +46 -103
  318. package/src/composables/useJobsStatusTray.ts +1 -1
  319. package/src/composables/useListReorder.story.vue +143 -0
  320. package/src/composables/useListReorder.ts +242 -0
  321. package/src/composables/useMenuKeyboard.ts +90 -0
  322. package/src/composables/useOptimisticMutation.ts +6 -16
  323. package/src/composables/usePluginClient.ts +91 -252
  324. package/src/composables/usePluginConfig.ts +3 -1
  325. package/src/composables/usePluginWorkspace.ts +208 -0
  326. package/src/composables/useRackEditor.ts +2 -11
  327. package/src/composables/useRequestSyncState.ts +98 -28
  328. package/src/composables/useResourceCrud.ts +6 -16
  329. package/src/composables/useRovingFocus.ts +66 -0
  330. package/src/composables/useScheduleDrag.ts +37 -0
  331. package/src/composables/useWellPainting.ts +10 -30
  332. package/src/composables/useWellPlateAdapter.ts +5 -1
  333. package/src/composables/useWellPlateEditor.ts +1 -11
  334. package/src/composables/useWellPlateValidation.ts +5 -1
  335. package/src/composables/workspaceRecipes.ts +267 -0
  336. package/src/index.ts +16 -0
  337. package/src/permissions.ts +48 -9
  338. package/src/stores/experiment.ts +2 -7
  339. package/src/stores/index.ts +9 -1
  340. package/src/stores/settings.ts +93 -1
  341. package/src/styles/components/audit-trail.css +5 -5
  342. package/src/styles/components/batch-progress-list.css +7 -0
  343. package/src/styles/components/color-slider.css +2 -2
  344. package/src/styles/components/data-state.css +30 -0
  345. package/src/styles/components/dataframe.css +29 -35
  346. package/src/styles/components/experiment-data-viewer.css +4 -4
  347. package/src/styles/components/experiment-timeline.css +3 -30
  348. package/src/styles/components/form-builder.css +5 -5
  349. package/src/styles/components/lcms-sequence-table.css +16 -1
  350. package/src/styles/components/multi-select.css +1 -1
  351. package/src/styles/components/reagent-list.css +38 -31
  352. package/src/styles/components/sample-hierarchy-tree.css +5 -19
  353. package/src/styles/components/sample-selector.css +9 -2
  354. package/src/styles/components/schedule-calendar.css +8 -0
  355. package/src/styles/components/scientific-number.css +1 -1
  356. package/src/styles/components/secret-input.css +60 -0
  357. package/src/styles/components/well-plate.css +8 -0
  358. package/src/styles/index.css +9 -0
  359. package/src/styles/variables.css +29 -0
  360. package/src/templates/builderPresetControls.ts +1 -1
  361. package/src/templates/builderReadUtils.ts +1 -3
  362. package/src/templates/componentPlateHelpers.ts +3 -1
  363. package/src/templates/componentTargetResolvers.ts +1 -3
  364. package/src/templates/controlSchemaTargets.ts +1 -3
  365. package/src/templates/doseResponseAdapters.ts +2 -13
  366. package/src/templates/elisaAssayCollectionBuilder.ts +2 -2
  367. package/src/templates/plateMapBuilder.ts +2 -2
  368. package/src/templates/qpcrPlateBuilder.ts +1 -1
  369. package/src/templates/templateValidators.ts +1 -1
  370. package/src/templates/timeCourseBuilder.ts +2 -2
  371. package/src/templates/wellPlateScreenCollectionBuilder.ts +2 -2
  372. package/src/types/form-builder.ts +1 -0
  373. package/src/utils/color.ts +51 -0
  374. package/src/utils/errors.ts +29 -0
  375. package/src/utils/formModelSync.ts +1 -3
  376. package/src/utils/pluginIcon.ts +1 -3
  377. package/src/utils/pluginSecrets.ts +102 -0
  378. package/src/utils/records.ts +9 -0
  379. package/dist/EmptyState-p90quq8Z.js.map +0 -1
  380. package/dist/ExperimentPopover-tLwJ7XF4.js.map +0 -1
  381. package/dist/ExperimentSelectorModal-B6P2msT4.js.map +0 -1
  382. package/dist/SettingsModal-BW3Ar-uy.js.map +0 -1
  383. package/dist/components-DQt0tcpY.js.map +0 -1
  384. package/dist/composables/autoGroupConstants.d.ts +0 -1
  385. package/dist/composables/autoGroupGrouping.d.ts +0 -3
  386. package/dist/composables/useAutoGroupInputSources.d.ts +0 -32
  387. package/dist/composables-CkXDe7nm.js.map +0 -1
  388. package/dist/experiment-Y9Iz7KYi.js.map +0 -1
  389. package/dist/experiment-utils-CLnzPPq5.js.map +0 -1
  390. package/dist/templates/builderDefaults.d.ts +0 -1
  391. package/dist/templates/plateGeometry.d.ts +0 -4
  392. package/dist/templates-CovvRZ-9.js.map +0 -1
  393. package/dist/useApi-CDBq6z23.js.map +0 -1
  394. package/dist/useControlSchema-BhoJzWzx.js.map +0 -1
  395. package/dist/useDropdownState-DSb3BC3N.js.map +0 -1
  396. package/dist/useProtocolTemplates-DHwSveE6.js.map +0 -1
  397. package/src/__tests__/composables/useAutoGroupInputSources.test.ts +0 -127
  398. package/src/composables/autoGroupConstants.ts +0 -4
  399. package/src/composables/autoGroupGrouping.ts +0 -148
  400. package/src/composables/useAutoGroupInputSources.ts +0 -147
  401. package/src/templates/builderDefaults.ts +0 -11
  402. package/src/templates/plateGeometry.ts +0 -62
  403. /package/dist/__tests__/{composables/useAutoGroupInputSources.test.d.ts → components/DataStates.test.d.ts} +0 -0
@@ -14,7 +14,6 @@ import {
14
14
  uploadPluginEndpoint,
15
15
  useCurrentExperiment,
16
16
  usePluginEventStream,
17
- usePluginClient,
18
17
  usePluginSettings,
19
18
  type PluginContract,
20
19
  } from '../../composables/usePluginClient'
@@ -80,7 +79,13 @@ function pendingEventStreamResponse(): {
80
79
  })
81
80
  return {
82
81
  response: new Response(body, { status: 200 }),
83
- close: () => streamController.close(),
82
+ close: () => {
83
+ try {
84
+ streamController.close()
85
+ } catch {
86
+ // stop() may already have cancelled the reader.
87
+ }
88
+ },
84
89
  }
85
90
  }
86
91
 
@@ -843,6 +848,146 @@ describe('usePluginClient', () => {
843
848
  })
844
849
  })
845
850
 
851
+ describe('secret settings references', () => {
852
+ const secretRef = { $secret: 'sec_0f2a4c6e8a0c2e4a6c8e0a2c4e6a8c0e', set: true }
853
+
854
+ function integratedPluginSettingsHost(): void {
855
+ ;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
856
+ isIntegrated: true,
857
+ theme: 'light',
858
+ platformApiUrl: '/api',
859
+ plugin: {
860
+ id: 'drp',
861
+ name: 'drp',
862
+ version: '1.0.0',
863
+ route_prefix: '/drp',
864
+ api_prefix: '/api/drp',
865
+ settings_api: 'plugin',
866
+ },
867
+ }
868
+ nextGetData = {
869
+ settings: { apiKey: secretRef, method: 'first' },
870
+ secret_format: 'ref',
871
+ mode: 'integrated',
872
+ revision: 'revision-1',
873
+ }
874
+ nextPutData = {
875
+ settings: { apiKey: secretRef, method: 'second' },
876
+ secret_format: 'ref',
877
+ mode: 'integrated',
878
+ revision: 'revision-2',
879
+ }
880
+ }
881
+
882
+ it('should round-trip an untouched secret reference unchanged', async () => {
883
+ integratedPluginSettingsHost()
884
+
885
+ const settings = usePluginSettings<{ apiKey?: unknown; method?: string }>({
886
+ pluginName: 'drp',
887
+ })
888
+ await settings.load()
889
+ const saved = await settings.save({ ...settings.values.value, method: 'second' })
890
+
891
+ expect({ saved, body: requestBodies[0] }).toEqual({
892
+ saved: true,
893
+ body: { apiKey: secretRef, method: 'second', secret_format: 'ref' },
894
+ })
895
+ })
896
+
897
+ it('should submit a typed string when the user enters a new secret', async () => {
898
+ integratedPluginSettingsHost()
899
+
900
+ const settings = usePluginSettings<{ apiKey?: unknown; method?: string }>({
901
+ pluginName: 'drp',
902
+ })
903
+ await settings.load()
904
+ await settings.save({ apiKey: 'typed-secret', method: 'first' })
905
+
906
+ expect(requestBodies[0]).toEqual({
907
+ apiKey: 'typed-secret',
908
+ method: 'first',
909
+ secret_format: 'ref',
910
+ })
911
+ })
912
+
913
+ it('should submit null when the user clears a secret', async () => {
914
+ integratedPluginSettingsHost()
915
+
916
+ const settings = usePluginSettings<{ apiKey?: unknown; method?: string }>({
917
+ pluginName: 'drp',
918
+ })
919
+ await settings.load()
920
+ await settings.save({ apiKey: null, method: 'first' })
921
+
922
+ expect(requestBodies[0]).toEqual({
923
+ apiKey: null,
924
+ method: 'first',
925
+ secret_format: 'ref',
926
+ })
927
+ })
928
+
929
+ it('should declare the reference format on platform config patches', async () => {
930
+ ;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
931
+ isIntegrated: true,
932
+ theme: 'light',
933
+ platformApiUrl: '/api',
934
+ plugin: {
935
+ id: 'drp',
936
+ name: 'drp',
937
+ version: '1.0.0',
938
+ route_prefix: '/drp',
939
+ api_prefix: '/api/drp',
940
+ },
941
+ }
942
+ nextGetData = {
943
+ plugin_name: 'drp',
944
+ config: { apiKey: secretRef, method: 'first' },
945
+ secret_format: 'ref',
946
+ revision: 'revision-1',
947
+ }
948
+ nextPatchData = {
949
+ plugin_name: 'drp',
950
+ config: { apiKey: secretRef, method: 'second' },
951
+ secret_format: 'ref',
952
+ revision: 'revision-2',
953
+ }
954
+
955
+ const settings = usePluginSettings<{ apiKey?: unknown; method?: string }>({
956
+ pluginName: 'drp',
957
+ })
958
+ await settings.load()
959
+ await settings.save({ apiKey: secretRef, method: 'second' })
960
+
961
+ expect(requestBodies[0]).toEqual({
962
+ config: { method: 'second', secret_format: 'ref' },
963
+ })
964
+ })
965
+
966
+ it('should omit the format marker for a host that does not publish one', async () => {
967
+ ;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
968
+ isIntegrated: true,
969
+ theme: 'light',
970
+ platformApiUrl: '/api',
971
+ plugin: {
972
+ id: 'drp',
973
+ name: 'drp',
974
+ version: '1.0.0',
975
+ route_prefix: '/drp',
976
+ api_prefix: '/api/drp',
977
+ settings_api: 'plugin',
978
+ },
979
+ }
980
+ nextGetData = { settings: { method: 'first' }, mode: 'integrated', revision: 'revision-1' }
981
+ nextPutData = { settings: { method: 'second' }, mode: 'integrated', revision: 'revision-2' }
982
+
983
+ const settings = usePluginSettings<{ method?: string }>({ pluginName: 'drp' })
984
+ await settings.load()
985
+ await settings.save({ method: 'second' })
986
+
987
+ expect(requestBodies[0]).toEqual({ method: 'second' })
988
+ })
989
+ })
990
+
846
991
  it('should preserve the contract v2 form schema in settingsConfig', () => {
847
992
  const settings = usePluginSettings({
848
993
  apiBaseUrl: '/api/drp',
@@ -1396,11 +1541,7 @@ describe('usePluginClient', () => {
1396
1541
 
1397
1542
  try {
1398
1543
  stream.start()
1399
- await Promise.resolve()
1400
- await Promise.resolve()
1401
- await Promise.resolve()
1402
-
1403
- expect(onOpen).toHaveBeenCalledOnce()
1544
+ await vi.waitFor(() => expect(onOpen).toHaveBeenCalledOnce())
1404
1545
  } finally {
1405
1546
  stopWatching()
1406
1547
  stream.stop()
@@ -1684,12 +1825,6 @@ describe('usePluginClient', () => {
1684
1825
  expect(requestConfigs).toHaveLength(0)
1685
1826
  })
1686
1827
 
1687
- it('returns the provided client identity from usePluginClient', () => {
1688
- const client = createPluginClient(contract, { endpoints: {} })
1689
-
1690
- expect(usePluginClient(client)).toBe(client)
1691
- })
1692
-
1693
1828
  it('loads the current experiment from URL conventions', async () => {
1694
1829
  window.history.replaceState({}, '', '/analysis?experimentId=42')
1695
1830
  const current = useCurrentExperiment<{ called: string }>({ immediate: false })
@@ -89,4 +89,144 @@ describe('useRequestSyncState', () => {
89
89
  expect(state.lastSavedAt.value).toBeNull()
90
90
  expect(state.lastRunAt.value).toBeNull()
91
91
  })
92
+
93
+ describe('stale-response guard', () => {
94
+ function deferred<T>() {
95
+ let resolve!: (value: T) => void
96
+ let reject!: (reason: unknown) => void
97
+ const promise = new Promise<T>((res, rej) => {
98
+ resolve = res
99
+ reject = rej
100
+ })
101
+ return { promise, resolve, reject }
102
+ }
103
+
104
+ it('lets only the newest run own loading', async () => {
105
+ const state = useRequestSyncState()
106
+ const first = deferred<string>()
107
+ const second = deferred<string>()
108
+
109
+ const firstRun = state.run(() => first.promise, { success: 'load' })
110
+ const secondRun = state.run(() => second.promise, { success: 'load' })
111
+
112
+ first.resolve('stale')
113
+ await expect(firstRun).resolves.toBe('stale')
114
+ expect(state.loading.value).toBe(true)
115
+
116
+ second.resolve('fresh')
117
+ await expect(secondRun).resolves.toBe('fresh')
118
+ expect(state.loading.value).toBe(false)
119
+ })
120
+
121
+ it('does not let a stale success move the sync timestamps', async () => {
122
+ const state = useRequestSyncState()
123
+ const first = deferred<string>()
124
+ const second = deferred<string>()
125
+
126
+ const firstRun = state.run(() => first.promise, { success: 'save' })
127
+ const secondRun = state.run(() => second.promise, { success: 'load' })
128
+
129
+ first.resolve('stale')
130
+ await firstRun
131
+ expect(state.lastSavedAt.value).toBeNull()
132
+
133
+ second.resolve('fresh')
134
+ await secondRun
135
+ expect(state.lastLoadedAt.value).toBeInstanceOf(Date)
136
+ expect(state.lastSavedAt.value).toBeNull()
137
+ })
138
+
139
+ it('does not let a stale failure write the error, but still rejects its caller', async () => {
140
+ const state = useRequestSyncState()
141
+ const first = deferred<string>()
142
+ const second = deferred<string>()
143
+
144
+ const firstRun = state.run(() => first.promise, { errorMessage: 'first failed' })
145
+ const secondRun = state.run(() => second.promise, { success: 'load' })
146
+
147
+ first.reject(new Error('stale failure'))
148
+ await expect(firstRun).rejects.toThrow('stale failure')
149
+ expect(state.error.value).toBeNull()
150
+
151
+ second.resolve('fresh')
152
+ await secondRun
153
+ expect(state.error.value).toBeNull()
154
+ expect(state.loading.value).toBe(false)
155
+ })
156
+
157
+ it('aborts the superseded run signal by default', async () => {
158
+ const state = useRequestSyncState()
159
+ const first = deferred<string>()
160
+ const second = deferred<string>()
161
+ let firstSignal: AbortSignal | undefined
162
+
163
+ const firstRun = state.run((signal) => {
164
+ firstSignal = signal
165
+ return first.promise
166
+ })
167
+ expect(firstSignal?.aborted).toBe(false)
168
+
169
+ const secondRun = state.run(() => second.promise)
170
+ expect(firstSignal?.aborted).toBe(true)
171
+
172
+ first.resolve('stale')
173
+ second.resolve('fresh')
174
+ await Promise.all([firstRun, secondRun])
175
+ })
176
+
177
+ it('leaves the previous signal alone when abortPrevious is false', async () => {
178
+ const state = useRequestSyncState()
179
+ const first = deferred<string>()
180
+ const second = deferred<string>()
181
+ let firstSignal: AbortSignal | undefined
182
+
183
+ const firstRun = state.run((signal) => {
184
+ firstSignal = signal
185
+ return first.promise
186
+ })
187
+ const secondRun = state.run(() => second.promise, { abortPrevious: false })
188
+
189
+ expect(firstSignal?.aborted).toBe(false)
190
+ first.resolve('stale')
191
+ second.resolve('fresh')
192
+ await Promise.all([firstRun, secondRun])
193
+ })
194
+
195
+ it('forwards a caller signal onto the operation signal', async () => {
196
+ const state = useRequestSyncState()
197
+ const caller = new AbortController()
198
+ const pending = deferred<string>()
199
+ let operationSignal: AbortSignal | undefined
200
+
201
+ const run = state.run((signal) => {
202
+ operationSignal = signal
203
+ return pending.promise
204
+ }, { signal: caller.signal })
205
+
206
+ caller.abort()
207
+ expect(operationSignal?.aborted).toBe(true)
208
+
209
+ pending.resolve('done')
210
+ await run
211
+ })
212
+
213
+ it('cancel() aborts the in-flight run and drops loading', async () => {
214
+ const state = useRequestSyncState()
215
+ const pending = deferred<string>()
216
+ let operationSignal: AbortSignal | undefined
217
+
218
+ const run = state.run((signal) => {
219
+ operationSignal = signal
220
+ return pending.promise
221
+ }, { success: 'load' })
222
+
223
+ state.cancel()
224
+ expect(operationSignal?.aborted).toBe(true)
225
+ expect(state.loading.value).toBe(false)
226
+
227
+ pending.resolve('too late')
228
+ await expect(run).resolves.toBe('too late')
229
+ expect(state.lastLoadedAt.value).toBeNull()
230
+ })
231
+ })
92
232
  })
@@ -22,7 +22,7 @@ interface FrontendManifest {
22
22
  }>
23
23
  composables: Array<{
24
24
  name: string
25
- members?: Array<{ name: string }>
25
+ members?: Array<{ name: string; type?: string }>
26
26
  examples?: Array<{ title: string; language: string; code: string }>
27
27
  }>
28
28
  templateExports: Array<{ name: string }>
@@ -271,6 +271,21 @@ describe('frontend docs catalog coverage', () => {
271
271
  })
272
272
  })
273
273
 
274
+ it('documents the source and MINT 1.1 JobsStatusTray props', () => {
275
+ const manifest = extractFrontendManifest()
276
+ const jobsStatusTray = manifest.categories.components.find(item => item.name === 'JobsStatusTray')
277
+
278
+ expect(jobsStatusTray?.props?.map(prop => prop.name).sort()).toEqual([
279
+ 'adapter',
280
+ 'eventStream',
281
+ 'jobs',
282
+ 'loadOnMount',
283
+ 'source',
284
+ 'teleportTo',
285
+ 'title',
286
+ ])
287
+ })
288
+
274
289
  it('re-exports every public component from the root SDK entrypoint', () => {
275
290
  const publicComponents = parseComponentExports()
276
291
  const rootComponents = parseRootComponentExports()
@@ -295,6 +310,7 @@ describe('frontend docs catalog coverage', () => {
295
310
  const rootIndex = readFileSync(join(SDK_ROOT, 'src', 'index.ts'), 'utf8')
296
311
  const componentsIndex = readFileSync(join(SDK_ROOT, 'src', 'components', 'index.ts'), 'utf8')
297
312
  const composablesIndex = readFileSync(join(SDK_ROOT, 'src', 'composables', 'index.ts'), 'utf8')
313
+ const composableExports = composablesIndex.replace(/from\s+['"][^'"]+['"]/g, '')
298
314
 
299
315
  const retiredNames = [
300
316
  'ToastNotification',
@@ -311,10 +327,26 @@ describe('frontend docs catalog coverage', () => {
311
327
  for (const name of retiredNames) {
312
328
  expect(rootIndex).not.toMatch(new RegExp(`\\b${name}\\b`))
313
329
  expect(componentsIndex).not.toMatch(new RegExp(`\\b${name}\\b`))
314
- expect(composablesIndex).not.toMatch(new RegExp(`\\b${name}\\b`))
330
+ expect(composableExports).not.toMatch(new RegExp(`\\b${name}\\b`))
315
331
  }
316
332
  })
317
333
 
334
+ it('keeps the MINT 1.1 composable compatibility exports', () => {
335
+ const composablesIndex = readFileSync(join(SDK_ROOT, 'src', 'composables', 'index.ts'), 'utf8')
336
+ const expected = [
337
+ 'useJobsStatusTray',
338
+ 'UseJobsStatusTrayOptions',
339
+ 'UseJobsStatusTrayReturn',
340
+ 'JobsStatusTrayAdapter',
341
+ 'JobStreamMessage',
342
+ 'usePluginConfig',
343
+ 'UsePluginConfigReturn',
344
+ 'usePluginClient',
345
+ ]
346
+
347
+ expect(expected.filter(name => !new RegExp(`\\b${name}\\b`).test(composablesIndex))).toEqual([])
348
+ })
349
+
318
350
  it('documents every public composable/helper export used by docs and doctor', () => {
319
351
  const manifest = extractFrontendManifest()
320
352
  const publicComposables = parseDocumentedComposableExports()
@@ -401,4 +433,25 @@ describe('frontend docs catalog coverage', () => {
401
433
 
402
434
  expect(useFormBuilder?.members?.map(member => member.name)).toContain('updateSchema')
403
435
  })
436
+
437
+ it('extracts inherited return interface members', () => {
438
+ const manifest = extractFrontendManifest()
439
+ const workspace = manifest.categories.composables.find(item => item.name === 'useBioTemplateWorkspace')
440
+
441
+ expect(workspace?.members?.map(member => member.name)).toContain('componentUsage')
442
+ })
443
+
444
+ it('ignores generic constraints when extracting inherited return members', () => {
445
+ const manifest = extractFrontendManifest()
446
+ const history = manifest.categories.composables.find(item => item.name === 'useCommandHistory')
447
+
448
+ expect(history?.members?.map(member => member.name)).not.toContain('description')
449
+ })
450
+
451
+ it('extracts exact array return member types while resolving inheritance', () => {
452
+ const manifest = extractFrontendManifest()
453
+ const bindings = manifest.categories.composables.find(item => item.name === 'controlValuesToComponentBindings')
454
+
455
+ expect(bindings?.members?.find(member => member.name === 'props')?.type).toBe('Record<string, unknown>')
456
+ })
404
457
  })
@@ -1,6 +1,12 @@
1
1
  import { beforeEach, describe, expect, it, vi } from 'vitest'
2
2
  import { createPinia, setActivePinia } from 'pinia'
3
- import { useSettingsStore } from '../../stores/settings'
3
+ import { nextTick } from 'vue'
4
+ import {
5
+ paletteCssVariables,
6
+ tableDensityToSize,
7
+ tryUseSettingsStore,
8
+ useSettingsStore,
9
+ } from '../../stores/settings'
4
10
 
5
11
  function mockMatchMedia(matches = false) {
6
12
  Object.defineProperty(window, 'matchMedia', {
@@ -46,6 +52,8 @@ describe('useSettingsStore', () => {
46
52
  setActivePinia(createPinia())
47
53
  installLocalStorage()
48
54
  document.documentElement.classList.remove('dark')
55
+ document.documentElement.removeAttribute('style')
56
+ delete document.documentElement.dataset.density
49
57
  mockMatchMedia(false)
50
58
  })
51
59
 
@@ -75,4 +83,66 @@ describe('useSettingsStore', () => {
75
83
  tableDensity: 'compact',
76
84
  }))
77
85
  })
86
+
87
+ describe('palette and density DOM sync', () => {
88
+ it('writes no palette overrides for the default palette', () => {
89
+ const settings = useSettingsStore()
90
+ settings.initialize()
91
+
92
+ expect(paletteCssVariables('default')).toEqual({})
93
+ expect(document.documentElement.style.getPropertyValue('--color-primary')).toBe('')
94
+ })
95
+
96
+ it('applies the stored palette hues to html on initialize', () => {
97
+ localStorage.setItem('mint-settings', JSON.stringify({ colorPalette: 'viridis' }))
98
+
99
+ const settings = useSettingsStore()
100
+ settings.initialize()
101
+
102
+ const style = document.documentElement.style
103
+ expect(style.getPropertyValue('--color-primary')).toBe('hsl(280 84% 67%)')
104
+ expect(style.getPropertyValue('--color-primary-hover')).toBe('hsl(280 75% 59%)')
105
+ expect(style.getPropertyValue('--color-cta')).toBe('hsl(80 95% 53%)')
106
+ })
107
+
108
+ it('re-applies the overrides when the palette changes and clears them on default', async () => {
109
+ const settings = useSettingsStore()
110
+ settings.initialize()
111
+
112
+ settings.colorPalette = 'pastel'
113
+ await nextTick()
114
+ expect(document.documentElement.style.getPropertyValue('--color-primary')).toBe('hsl(200 84% 67%)')
115
+
116
+ settings.colorPalette = 'default'
117
+ await nextTick()
118
+ expect(document.documentElement.style.getPropertyValue('--color-primary')).toBe('')
119
+ expect(document.documentElement.style.getPropertyValue('--color-primary-soft')).toBe('')
120
+ })
121
+
122
+ it('mirrors the table density onto html[data-density]', async () => {
123
+ const settings = useSettingsStore()
124
+ settings.initialize()
125
+ expect(document.documentElement.dataset.density).toBe('normal')
126
+
127
+ settings.tableDensity = 'compact'
128
+ await nextTick()
129
+ expect(document.documentElement.dataset.density).toBe('compact')
130
+ })
131
+
132
+ it('maps each density onto a table size', () => {
133
+ const settings = useSettingsStore()
134
+ settings.initialize()
135
+
136
+ expect(tableDensityToSize('compact')).toBe('sm')
137
+ expect(tableDensityToSize('normal')).toBe('md')
138
+ expect(tableDensityToSize('comfortable')).toBe('lg')
139
+
140
+ settings.tableDensity = 'comfortable'
141
+ expect(settings.getTableSize()).toBe('lg')
142
+ })
143
+ })
144
+
145
+ it('exposes the store through tryUseSettingsStore when Pinia is active', () => {
146
+ expect(tryUseSettingsStore()).not.toBeNull()
147
+ })
78
148
  })
@@ -0,0 +1,131 @@
1
+ import { readdirSync, readFileSync, statSync } from 'node:fs'
2
+ import { dirname, join, relative, resolve } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { describe, expect, it } from 'vitest'
5
+
6
+ const here = dirname(fileURLToPath(import.meta.url))
7
+ const srcRoot = resolve(here, '../..')
8
+ const stylesDir = join(srcRoot, 'styles')
9
+ const variablesCss = readFileSync(join(stylesDir, 'variables.css'), 'utf-8')
10
+
11
+ /**
12
+ * Custom properties that are legitimately *not* declared in variables.css:
13
+ * they are set at runtime (inline `:style` bindings, JS-built names) or are
14
+ * component-local knobs declared in the same stylesheet that consumes them.
15
+ * Anything else must be a canonical token from variables.css.
16
+ */
17
+ const RUNTIME_PROPERTY_ALLOWLIST = new Set<string>([
18
+ // Names assembled in JS (`--grp-${i}`) — the literal prefix is what the
19
+ // scanner sees in a template string.
20
+ '--grp-',
21
+ ])
22
+
23
+ const SOURCE_EXTENSIONS = ['.vue', '.css', '.ts']
24
+ const IGNORED_DIRS = new Set(['__tests__', 'generated-ui', 'node_modules'])
25
+
26
+ function walk(dir: string, out: string[] = []): string[] {
27
+ for (const entry of readdirSync(dir)) {
28
+ const full = join(dir, entry)
29
+ if (statSync(full).isDirectory()) {
30
+ if (!IGNORED_DIRS.has(entry)) walk(full, out)
31
+ } else if (SOURCE_EXTENSIONS.some((ext) => entry.endsWith(ext))) {
32
+ out.push(full)
33
+ }
34
+ }
35
+ return out
36
+ }
37
+
38
+ const sourceFiles = walk(srcRoot)
39
+
40
+ function declaredIn(text: string): Set<string> {
41
+ const names = new Set<string>()
42
+ // `--foo: value` declarations, in CSS blocks and in JS style objects.
43
+ for (const m of text.matchAll(/(--[A-Za-z0-9_-]+)\s*:/g)) names.add(m[1])
44
+ // setProperty('--foo', …) / `'--foo'` keys in style bindings.
45
+ for (const m of text.matchAll(/['"`](--[A-Za-z0-9_-]+)['"`]/g)) names.add(m[1])
46
+ return names
47
+ }
48
+
49
+ const canonicalTokens = declaredIn(variablesCss)
50
+
51
+ // Component-local custom properties: declared in a stylesheet or set from an
52
+ // inline `:style` binding in the SFC that owns it. They are legitimate, they
53
+ // just are not part of the global token set.
54
+ const componentLocalProperties = new Set<string>()
55
+ for (const file of sourceFiles) {
56
+ for (const name of declaredIn(readFileSync(file, 'utf-8'))) {
57
+ componentLocalProperties.add(name)
58
+ }
59
+ }
60
+
61
+ interface Usage {
62
+ file: string
63
+ token: string
64
+ }
65
+
66
+ const usages: Usage[] = []
67
+ for (const file of sourceFiles) {
68
+ const text = readFileSync(file, 'utf-8')
69
+ for (const m of text.matchAll(/var\(\s*(--[A-Za-z0-9_-]+)/g)) {
70
+ usages.push({ file, token: m[1] })
71
+ }
72
+ }
73
+
74
+ describe('CSS custom properties', () => {
75
+ it('scans a meaningful number of var() references', () => {
76
+ expect(usages.length).toBeGreaterThan(500)
77
+ expect(canonicalTokens.size).toBeGreaterThan(70)
78
+ })
79
+
80
+ it('only references custom properties that are actually defined', () => {
81
+ const undefinedUsages = usages.filter(({ token }) => {
82
+ if (canonicalTokens.has(token)) return false
83
+ if (RUNTIME_PROPERTY_ALLOWLIST.has(token)) return false
84
+ return !componentLocalProperties.has(token)
85
+ })
86
+
87
+ const report = [
88
+ ...new Set(
89
+ undefinedUsages.map(
90
+ ({ file, token }) => `${token} (${relative(srcRoot, file)})`,
91
+ ),
92
+ ),
93
+ ].sort()
94
+
95
+ expect(report).toEqual([])
96
+ })
97
+
98
+ it('does not use the platform-only --color-bg-*/--color-text-*/--color-border aliases', () => {
99
+ const platformOnly = /^--color-(bg|text)-|^--color-border$/
100
+ const offenders = [
101
+ ...new Set(
102
+ usages
103
+ .filter(({ token }) => platformOnly.test(token))
104
+ .map(({ file, token }) => `${token} (${relative(srcRoot, file)})`),
105
+ ),
106
+ ].sort()
107
+
108
+ expect(offenders).toEqual([])
109
+ })
110
+
111
+ it('aggregates every component stylesheet in styles/index.css', () => {
112
+ const index = readFileSync(join(stylesDir, 'index.css'), 'utf-8')
113
+ const imported = new Set(
114
+ [...index.matchAll(/@import\s+'\.\/components\/([^']+)'/g)].map((m) => m[1]),
115
+ )
116
+ const onDisk = readdirSync(join(stylesDir, 'components')).filter((f) =>
117
+ f.endsWith('.css'),
118
+ )
119
+ expect([...onDisk].filter((f) => !imported.has(f)).sort()).toEqual([])
120
+ })
121
+
122
+ it('does not re-import aggregated stylesheets from SFC <style> blocks', () => {
123
+ const offenders = sourceFiles
124
+ .filter((f) => f.endsWith('.vue'))
125
+ .filter((f) => /@import\s+['"][^'"]*styles\/components\//.test(readFileSync(f, 'utf-8')))
126
+ .map((f) => relative(srcRoot, f))
127
+ .sort()
128
+
129
+ expect(offenders).toEqual([])
130
+ })
131
+ })