@morscherlab/mint-sdk 1.0.0-rc.2 → 1.0.0-rc.5

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 (443) hide show
  1. package/dist/__tests__/components/AppTopBar.navigation.test.d.ts +1 -0
  2. package/dist/__tests__/components/DoseCalculatorVolumeField.test.d.ts +1 -0
  3. package/dist/__tests__/components/PlateMapEditorToolbarInternal.test.d.ts +1 -0
  4. package/dist/__tests__/components/PluginWorkspaceView.controls.test.d.ts +1 -0
  5. package/dist/__tests__/components/PluginWorkspaceView.navigation.test.d.ts +1 -0
  6. package/dist/__tests__/components/PluginWorkspaceView.shell.test.d.ts +1 -0
  7. package/dist/__tests__/components/ProtocolStep.presentation.test.d.ts +1 -0
  8. package/dist/__tests__/components/ProtocolStepEditor.state.test.d.ts +1 -0
  9. package/dist/__tests__/components/ProtocolStepParameterField.test.d.ts +1 -0
  10. package/dist/__tests__/components/ReagentList.presentation.test.d.ts +1 -0
  11. package/dist/__tests__/components/SampleSelector.colors.test.d.ts +1 -0
  12. package/dist/__tests__/components/SampleSelector.drag.test.d.ts +1 -0
  13. package/dist/__tests__/components/SampleSelector.groups.test.d.ts +1 -0
  14. package/dist/__tests__/components/SampleSelector.selection.test.d.ts +1 -0
  15. package/dist/__tests__/components/SampleSelectorSampleRow.test.d.ts +1 -0
  16. package/dist/__tests__/components/ScheduleCalendar.test.d.ts +1 -0
  17. package/dist/__tests__/components/SettingsModal.schema.test.d.ts +1 -0
  18. package/dist/__tests__/components/WellPlate.colors.test.d.ts +1 -0
  19. package/dist/__tests__/components/WellPlate.conditions.test.d.ts +1 -0
  20. package/dist/__tests__/components/WellPlate.geometry.test.d.ts +1 -0
  21. package/dist/__tests__/components/WellPlate.interaction.test.d.ts +1 -0
  22. package/dist/__tests__/components/WellPlate.legend.test.d.ts +1 -0
  23. package/dist/__tests__/components/WellPlate.rendering.test.d.ts +1 -0
  24. package/dist/__tests__/components/WellPlate.sampleDrop.test.d.ts +1 -0
  25. package/dist/__tests__/composables/autoGroup/classify.test.d.ts +1 -0
  26. package/dist/__tests__/composables/autoGroup/columns.test.d.ts +1 -0
  27. package/dist/__tests__/composables/autoGroup/compose.test.d.ts +1 -0
  28. package/dist/__tests__/composables/autoGroup/cooccurrence.test.d.ts +1 -0
  29. package/dist/__tests__/composables/autoGroup/fingerprint.test.d.ts +1 -0
  30. package/dist/__tests__/composables/autoGroup/integration.test.d.ts +1 -0
  31. package/dist/__tests__/composables/autoGroup/template.test.d.ts +1 -0
  32. package/dist/__tests__/composables/autoGroup/tokenize.test.d.ts +1 -0
  33. package/dist/__tests__/composables/useAutoGroupInputSources.test.d.ts +1 -0
  34. package/dist/__tests__/composables/useScheduleCalendarLayout.test.d.ts +1 -0
  35. package/dist/__tests__/docs/extractDocsComponents.test.d.ts +1 -0
  36. package/dist/__tests__/docs/extractDocsExports.test.d.ts +1 -0
  37. package/dist/__tests__/docs/extractDocsParsing.test.d.ts +1 -0
  38. package/dist/__tests__/docs/extractDocsTemplates.test.d.ts +1 -0
  39. package/dist/__tests__/docs/extractDocsTheme.test.d.ts +1 -0
  40. package/dist/components/AppTopBar.navigation.d.ts +11 -0
  41. package/dist/components/BaseButton.vue.d.ts +1 -1
  42. package/dist/components/BaseCheckbox.vue.d.ts +1 -1
  43. package/dist/components/BaseInput.vue.d.ts +2 -2
  44. package/dist/components/BasePill.vue.d.ts +1 -1
  45. package/dist/components/BaseRadioGroup.vue.d.ts +1 -1
  46. package/dist/components/BaseSelect.vue.d.ts +1 -1
  47. package/dist/components/BaseSlider.vue.d.ts +2 -2
  48. package/dist/components/BaseTextarea.vue.d.ts +2 -2
  49. package/dist/components/BaseToggle.vue.d.ts +1 -1
  50. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +2 -2
  51. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -1
  52. package/dist/components/ColorSlider.vue.d.ts +2 -2
  53. package/dist/components/ConcentrationInput.vue.d.ts +2 -2
  54. package/dist/components/DatePicker.vue.d.ts +1 -1
  55. package/dist/components/DateTimePicker.vue.d.ts +2 -2
  56. package/dist/components/DoseCalculatorVolumeField.vue.d.ts +15 -0
  57. package/dist/components/DropdownButton.vue.d.ts +1 -1
  58. package/dist/components/FileUploader.vue.d.ts +2 -2
  59. package/dist/components/FormulaInput.vue.d.ts +2 -2
  60. package/dist/components/IconButton.vue.d.ts +1 -1
  61. package/dist/components/LoadingSpinner.vue.d.ts +1 -1
  62. package/dist/components/MoleculeInput.vue.d.ts +2 -2
  63. package/dist/components/MultiSelect.vue.d.ts +1 -1
  64. package/dist/components/NumberInput.vue.d.ts +1 -1
  65. package/dist/components/PlateMapEditor.vue.d.ts +6 -6
  66. package/dist/components/PluginWorkspaceView.controls.d.ts +28 -0
  67. package/dist/components/PluginWorkspaceView.navigation.d.ts +29 -0
  68. package/dist/components/PluginWorkspaceView.props.d.ts +151 -0
  69. package/dist/components/PluginWorkspaceView.shell.d.ts +19 -0
  70. package/dist/components/PluginWorkspaceView.vue.d.ts +46 -195
  71. package/dist/components/ProgressBar.vue.d.ts +1 -1
  72. package/dist/components/ProtocolStep.presentation.d.ts +4 -0
  73. package/dist/components/ProtocolStepEditor.state.d.ts +18 -0
  74. package/dist/components/ProtocolStepParameterField.vue.d.ts +12 -0
  75. package/dist/components/ReagentList.presentation.d.ts +16 -0
  76. package/dist/components/ResourceCard.vue.d.ts +1 -1
  77. package/dist/components/SampleSelector.colors.d.ts +13 -0
  78. package/dist/components/SampleSelector.drag.d.ts +24 -0
  79. package/dist/components/SampleSelector.groups.d.ts +15 -0
  80. package/dist/components/SampleSelector.selection.d.ts +26 -0
  81. package/dist/components/SampleSelector.vue.d.ts +4 -1
  82. package/dist/components/SampleSelectorSampleRow.vue.d.ts +21 -0
  83. package/dist/components/SegmentedControl.vue.d.ts +1 -1
  84. package/dist/components/SequenceInput.vue.d.ts +2 -2
  85. package/dist/components/SequenceProgressBar.vue.d.ts +1 -1
  86. package/dist/components/SettingsModal.schema.d.ts +9 -0
  87. package/dist/components/StatusIndicator.vue.d.ts +1 -1
  88. package/dist/components/TagsInput.vue.d.ts +2 -2
  89. package/dist/components/TimePicker.vue.d.ts +2 -2
  90. package/dist/components/TimeRangeInput.vue.d.ts +1 -1
  91. package/dist/components/UnitInput.vue.d.ts +2 -2
  92. package/dist/components/WellPlate.colors.d.ts +9 -0
  93. package/dist/components/WellPlate.conditions.d.ts +26 -0
  94. package/dist/components/WellPlate.geometry.d.ts +23 -0
  95. package/dist/components/WellPlate.interaction.d.ts +71 -0
  96. package/dist/components/WellPlate.legend.d.ts +2 -0
  97. package/dist/components/WellPlate.rendering.d.ts +24 -0
  98. package/dist/components/WellPlate.sampleDrop.d.ts +8 -0
  99. package/dist/components/WellPlate.vue.d.ts +1 -1
  100. package/dist/components/index.js +2 -2
  101. package/dist/components/internal/ActionItemInternal.vue.d.ts +1 -1
  102. package/dist/components/internal/PlateMapEditorToolbarInternal.vue.d.ts +28 -0
  103. package/dist/{components-BhK-dW99.js → components-DtHA2bgp.js} +3754 -2991
  104. package/dist/components-DtHA2bgp.js.map +1 -0
  105. package/dist/composables/autoGroup/classKey.d.ts +4 -0
  106. package/dist/composables/autoGroup/classify.d.ts +28 -0
  107. package/dist/composables/autoGroup/colors.d.ts +2 -0
  108. package/dist/composables/autoGroup/columns.d.ts +10 -0
  109. package/dist/composables/autoGroup/compose.d.ts +8 -0
  110. package/dist/composables/autoGroup/cooccurrence.d.ts +2 -0
  111. package/dist/composables/autoGroup/csv-shim.d.ts +2 -0
  112. package/dist/composables/autoGroup/fingerprint.d.ts +3 -0
  113. package/dist/composables/autoGroup/index.d.ts +16 -0
  114. package/dist/composables/autoGroup/replicatePreGroup.d.ts +38 -0
  115. package/dist/composables/autoGroup/template.d.ts +15 -0
  116. package/dist/composables/autoGroup/tokenize.d.ts +8 -0
  117. package/dist/composables/autoGroupConstants.d.ts +1 -0
  118. package/dist/composables/autoGroupGrouping.d.ts +3 -0
  119. package/dist/composables/controlComponentBindings.d.ts +7 -0
  120. package/dist/composables/controlSchemaAdapters.d.ts +20 -0
  121. package/dist/composables/controlSchemaDoseDesign.d.ts +11 -0
  122. package/dist/composables/controlSchemaFormFields.d.ts +3 -0
  123. package/dist/composables/controlSchemaLayout.d.ts +7 -0
  124. package/dist/composables/controlSchemaModel.d.ts +5 -0
  125. package/dist/composables/controlSchemaNormalize.d.ts +15 -0
  126. package/dist/composables/controlSchemaTypes.d.ts +305 -0
  127. package/dist/composables/controlSchemaUtils.d.ts +9 -0
  128. package/dist/composables/controlWorkspaceOptions.d.ts +2 -0
  129. package/dist/composables/formBuilderSchema.d.ts +18 -0
  130. package/dist/composables/index.js +3 -3
  131. package/dist/composables/pluginEndpointBuilder.d.ts +13 -0
  132. package/dist/composables/protocolTemplateCatalog.d.ts +26 -0
  133. package/dist/composables/useAutoGroup.d.ts +61 -74
  134. package/dist/composables/useAutoGroupInputSources.d.ts +32 -0
  135. package/dist/composables/useBioTemplateControls.d.ts +1 -1
  136. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +1 -1
  137. package/dist/composables/useBioTemplateWorkspace.d.ts +1 -1
  138. package/dist/composables/useControlSchema.d.ts +8 -346
  139. package/dist/composables/useControlWorkspace.d.ts +5 -0
  140. package/dist/composables/useForm.d.ts +2 -33
  141. package/dist/composables/useFormBuilder.d.ts +2 -9
  142. package/dist/composables/useFormValidation.d.ts +34 -0
  143. package/dist/composables/usePluginClient.d.ts +1 -4
  144. package/dist/composables/useProtocolTemplates.d.ts +2 -24
  145. package/dist/composables/useScheduleCalendarLayout.d.ts +49 -0
  146. package/dist/{composables-Bg7CFuNz.js → composables-Dlg8jenH.js} +33 -31
  147. package/dist/composables-Dlg8jenH.js.map +1 -0
  148. package/dist/index.js +4 -4
  149. package/dist/install.js +2 -2
  150. package/dist/styles.css +547 -516
  151. package/dist/templates/adapters.d.ts +14 -47
  152. package/dist/templates/assayLookups.d.ts +3 -0
  153. package/dist/templates/assayMatrixAdapters.d.ts +6 -0
  154. package/dist/templates/assayMatrixBuilder.d.ts +2 -0
  155. package/dist/templates/assayNormalizers.d.ts +4 -0
  156. package/dist/templates/builderDefaults.d.ts +1 -0
  157. package/dist/templates/builderIdUtils.d.ts +4 -0
  158. package/dist/templates/builderPresetControls.d.ts +10 -0
  159. package/dist/templates/builderReadUtils.d.ts +8 -0
  160. package/dist/templates/builders.d.ts +26 -67
  161. package/dist/templates/calibrationCurveAdapters.d.ts +4 -0
  162. package/dist/templates/calibrationCurveBuilder.d.ts +2 -0
  163. package/dist/templates/calibrationNormalizers.d.ts +5 -0
  164. package/dist/templates/componentBindingCatalog.d.ts +25 -0
  165. package/dist/templates/componentBindingHelpers.d.ts +17 -0
  166. package/dist/templates/componentDoseResponseProps.d.ts +3 -0
  167. package/dist/templates/componentGenericProps.d.ts +8 -0
  168. package/dist/templates/componentPlateHelpers.d.ts +5 -0
  169. package/dist/templates/componentPlateMapProps.d.ts +3 -0
  170. package/dist/templates/componentPropsFactory.d.ts +3 -0
  171. package/dist/templates/componentQpcrPlateProps.d.ts +3 -0
  172. package/dist/templates/componentTargetResolvers.d.ts +5 -0
  173. package/dist/templates/componentTemplateProps.d.ts +3 -0
  174. package/dist/templates/controlSchemaClone.d.ts +4 -0
  175. package/dist/templates/controlSchemaConstants.d.ts +10 -0
  176. package/dist/templates/controlSchemaMerge.d.ts +3 -0
  177. package/dist/templates/controlSchemaTargets.d.ts +17 -0
  178. package/dist/templates/controlSchemaTypes.d.ts +4 -0
  179. package/dist/templates/controlSchemas.d.ts +4 -4
  180. package/dist/templates/defaultBioTemplateBuilder.d.ts +2 -0
  181. package/dist/templates/doseResponseAdapters.d.ts +4 -0
  182. package/dist/templates/doseResponseBuilder.d.ts +2 -0
  183. package/dist/templates/elisaAssayCollectionBuilder.d.ts +2 -0
  184. package/dist/templates/flowCytometryAssayCollectionBuilder.d.ts +2 -0
  185. package/dist/templates/flowCytometryPanelBuilder.d.ts +2 -0
  186. package/dist/templates/flowNormalizers.d.ts +8 -0
  187. package/dist/templates/flowPanelAdapters.d.ts +4 -0
  188. package/dist/templates/index.js +1 -1
  189. package/dist/templates/instrumentRunAdapterHelpers.d.ts +8 -0
  190. package/dist/templates/instrumentRunAdapters.d.ts +8 -0
  191. package/dist/templates/instrumentRunBuilder.d.ts +2 -0
  192. package/dist/templates/lcmsBatchCollectionBuilder.d.ts +2 -0
  193. package/dist/templates/plateGeometry.d.ts +4 -0
  194. package/dist/templates/plateMapAdapters.d.ts +3 -0
  195. package/dist/templates/plateMapBuilder.d.ts +2 -0
  196. package/dist/templates/presetControlSchemas.d.ts +534 -0
  197. package/dist/templates/protocolAdapters.d.ts +5 -0
  198. package/dist/templates/protocolNormalizers.d.ts +6 -0
  199. package/dist/templates/protocolStepsBuilder.d.ts +2 -0
  200. package/dist/templates/qpcrAdapters.d.ts +5 -0
  201. package/dist/templates/qpcrExpressionCollectionBuilder.d.ts +2 -0
  202. package/dist/templates/qpcrPlateBuilder.d.ts +2 -0
  203. package/dist/templates/reagentAdapters.d.ts +5 -0
  204. package/dist/templates/reagentListBuilder.d.ts +2 -0
  205. package/dist/templates/runNormalizers.d.ts +10 -0
  206. package/dist/templates/sampleNormalizers.d.ts +4 -0
  207. package/dist/templates/samplePrepAdapters.d.ts +4 -0
  208. package/dist/templates/samplePrepBuilder.d.ts +2 -0
  209. package/dist/templates/sampleSheetAdapters.d.ts +5 -0
  210. package/dist/templates/sampleSheetBuilder.d.ts +2 -0
  211. package/dist/templates/targetedMetabolomicsCollectionBuilder.d.ts +2 -0
  212. package/dist/templates/targetedMetabolomicsHelpers.d.ts +5 -0
  213. package/dist/templates/templateAdapterTypes.d.ts +48 -0
  214. package/dist/templates/templateControlSchemas.d.ts +400 -0
  215. package/dist/templates/templateCreateOptions.d.ts +165 -0
  216. package/dist/templates/templateEnvelopes.d.ts +9 -0
  217. package/dist/templates/templatePackCollectionBuilder.d.ts +2 -0
  218. package/dist/templates/templatePresetCollectionBuilder.d.ts +18 -0
  219. package/dist/templates/templateQpcrTypes.d.ts +42 -0
  220. package/dist/templates/templateValidators.d.ts +13 -0
  221. package/dist/templates/timeCourseAdapters.d.ts +5 -0
  222. package/dist/templates/timeCourseBuilder.d.ts +2 -0
  223. package/dist/templates/types.d.ts +5 -250
  224. package/dist/templates/wellPlateScreenCollectionBuilder.d.ts +2 -0
  225. package/dist/templates/westernBlotAssayCollectionBuilder.d.ts +2 -0
  226. package/dist/{templates-BorLR_7p.js → templates-DtdUvJ4c.js} +3565 -3411
  227. package/dist/templates-DtdUvJ4c.js.map +1 -0
  228. package/dist/types/auto-group.d.ts +79 -9
  229. package/dist/types/componentLabTypes.d.ts +161 -0
  230. package/dist/types/componentWorkflowTypes.d.ts +150 -0
  231. package/dist/types/components.d.ts +2 -311
  232. package/dist/{useProtocolTemplates-n6AJqSqv.js → useProtocolTemplates-Bm5vyH4_.js} +1220 -454
  233. package/dist/useProtocolTemplates-Bm5vyH4_.js.map +1 -0
  234. package/package.json +1 -1
  235. package/src/__tests__/components/AppTopBar.navigation.test.ts +70 -0
  236. package/src/__tests__/components/DoseCalculatorVolumeField.test.ts +53 -0
  237. package/src/__tests__/components/PlateMapEditorToolbarInternal.test.ts +54 -0
  238. package/src/__tests__/components/PluginWorkspaceView.controls.test.ts +156 -0
  239. package/src/__tests__/components/PluginWorkspaceView.navigation.test.ts +102 -0
  240. package/src/__tests__/components/PluginWorkspaceView.shell.test.ts +41 -0
  241. package/src/__tests__/components/ProtocolStep.presentation.test.ts +31 -0
  242. package/src/__tests__/components/ProtocolStepEditor.state.test.ts +165 -0
  243. package/src/__tests__/components/ProtocolStepParameterField.test.ts +44 -0
  244. package/src/__tests__/components/ReagentList.presentation.test.ts +68 -0
  245. package/src/__tests__/components/SampleSelector.colors.test.ts +49 -0
  246. package/src/__tests__/components/SampleSelector.drag.test.ts +100 -0
  247. package/src/__tests__/components/SampleSelector.groups.test.ts +81 -0
  248. package/src/__tests__/components/SampleSelector.selection.test.ts +70 -0
  249. package/src/__tests__/components/SampleSelector.test.ts +32 -0
  250. package/src/__tests__/components/SampleSelectorSampleRow.test.ts +37 -0
  251. package/src/__tests__/components/ScheduleCalendar.test.ts +44 -0
  252. package/src/__tests__/components/SettingsModal.schema.test.ts +97 -0
  253. package/src/__tests__/components/WellPlate.colors.test.ts +28 -0
  254. package/src/__tests__/components/WellPlate.conditions.test.ts +68 -0
  255. package/src/__tests__/components/WellPlate.geometry.test.ts +54 -0
  256. package/src/__tests__/components/WellPlate.interaction.test.ts +171 -0
  257. package/src/__tests__/components/WellPlate.legend.test.ts +13 -0
  258. package/src/__tests__/components/WellPlate.rendering.test.ts +122 -0
  259. package/src/__tests__/components/WellPlate.sampleDrop.test.ts +70 -0
  260. package/src/__tests__/composables/autoGroup/classify.test.ts +107 -0
  261. package/src/__tests__/composables/autoGroup/columns.test.ts +135 -0
  262. package/src/__tests__/composables/autoGroup/compose.test.ts +227 -0
  263. package/src/__tests__/composables/autoGroup/cooccurrence.test.ts +91 -0
  264. package/src/__tests__/composables/autoGroup/fingerprint.test.ts +50 -0
  265. package/src/__tests__/composables/autoGroup/integration.test.ts +79 -0
  266. package/src/__tests__/composables/autoGroup/template.test.ts +70 -0
  267. package/src/__tests__/composables/autoGroup/tokenize.test.ts +33 -0
  268. package/src/__tests__/composables/useAutoGroup.test.ts +129 -625
  269. package/src/__tests__/composables/useAutoGroupInputSources.test.ts +107 -0
  270. package/src/__tests__/composables/useControlSchema.test.ts +23 -0
  271. package/src/__tests__/composables/useScheduleCalendarLayout.test.ts +89 -0
  272. package/src/__tests__/docs/extractDocsComponents.test.ts +142 -0
  273. package/src/__tests__/docs/extractDocsExports.test.ts +77 -0
  274. package/src/__tests__/docs/extractDocsParsing.test.ts +69 -0
  275. package/src/__tests__/docs/extractDocsTemplates.test.ts +54 -0
  276. package/src/__tests__/docs/extractDocsTheme.test.ts +89 -0
  277. package/src/__tests__/docs/frontendDocsCatalog.test.ts +1 -1
  278. package/src/__tests__/fixtures/auto-group/mixed-lc-ms-batch.txt +187 -0
  279. package/src/components/AppSidebar.vue +2 -6
  280. package/src/components/AppTopBar.navigation.ts +62 -0
  281. package/src/components/AppTopBar.vue +17 -44
  282. package/src/components/AutoGroupModal.story.vue +50 -0
  283. package/src/components/AutoGroupModal.vue +441 -158
  284. package/src/components/ControlWorkspaceView.vue +2 -6
  285. package/src/components/DoseCalculator.vue +13 -73
  286. package/src/components/DoseCalculatorVolumeField.vue +61 -0
  287. package/src/components/ExperimentTimeline.vue +6 -31
  288. package/src/components/FormBuilder.vue +2 -7
  289. package/src/components/PlateMapEditor.vue +32 -106
  290. package/src/components/PluginWorkspaceView.controls.ts +182 -0
  291. package/src/components/PluginWorkspaceView.navigation.ts +106 -0
  292. package/src/components/PluginWorkspaceView.props.ts +174 -0
  293. package/src/components/PluginWorkspaceView.shell.ts +66 -0
  294. package/src/components/PluginWorkspaceView.vue +85 -404
  295. package/src/components/ProtocolStep.presentation.ts +31 -0
  296. package/src/components/ProtocolStepEditor.state.ts +104 -0
  297. package/src/components/ProtocolStepEditor.vue +48 -179
  298. package/src/components/ProtocolStepParameterField.vue +134 -0
  299. package/src/components/ReagentList.presentation.ts +105 -0
  300. package/src/components/ReagentList.vue +16 -79
  301. package/src/components/SampleSelector.colors.ts +43 -0
  302. package/src/components/SampleSelector.drag.ts +164 -0
  303. package/src/components/SampleSelector.groups.ts +109 -0
  304. package/src/components/SampleSelector.selection.ts +103 -0
  305. package/src/components/SampleSelector.vue +82 -349
  306. package/src/components/SampleSelectorSampleRow.vue +64 -0
  307. package/src/components/ScheduleCalendar.vue +44 -199
  308. package/src/components/SettingsModal.schema.ts +71 -0
  309. package/src/components/SettingsModal.vue +16 -46
  310. package/src/components/WellPlate.colors.ts +56 -0
  311. package/src/components/WellPlate.conditions.ts +100 -0
  312. package/src/components/WellPlate.geometry.ts +91 -0
  313. package/src/components/WellPlate.interaction.ts +272 -0
  314. package/src/components/WellPlate.legend.ts +8 -0
  315. package/src/components/WellPlate.rendering.ts +105 -0
  316. package/src/components/WellPlate.sampleDrop.ts +73 -0
  317. package/src/components/WellPlate.vue +102 -550
  318. package/src/components/internal/PlateMapEditorToolbarInternal.vue +128 -0
  319. package/src/composables/autoGroup/classKey.ts +5 -0
  320. package/src/composables/autoGroup/classify.ts +205 -0
  321. package/src/composables/autoGroup/colors.ts +6 -0
  322. package/src/composables/autoGroup/columns.ts +226 -0
  323. package/src/composables/autoGroup/compose.ts +156 -0
  324. package/src/composables/autoGroup/cooccurrence.ts +46 -0
  325. package/src/composables/autoGroup/csv-shim.ts +44 -0
  326. package/src/composables/autoGroup/fingerprint.ts +49 -0
  327. package/src/composables/autoGroup/index.ts +20 -0
  328. package/src/composables/autoGroup/replicatePreGroup.ts +90 -0
  329. package/src/composables/autoGroup/template.ts +126 -0
  330. package/src/composables/autoGroup/tokenize.ts +41 -0
  331. package/src/composables/autoGroup/vocab.json +67 -0
  332. package/src/composables/autoGroupConstants.ts +4 -0
  333. package/src/composables/autoGroupGrouping.ts +148 -0
  334. package/src/composables/controlComponentBindings.ts +80 -0
  335. package/src/composables/controlSchemaAdapters.ts +196 -0
  336. package/src/composables/controlSchemaDoseDesign.ts +215 -0
  337. package/src/composables/controlSchemaFormFields.ts +61 -0
  338. package/src/composables/controlSchemaLayout.ts +59 -0
  339. package/src/composables/controlSchemaModel.ts +163 -0
  340. package/src/composables/controlSchemaNormalize.ts +101 -0
  341. package/src/composables/controlSchemaTypes.ts +364 -0
  342. package/src/composables/controlSchemaUtils.ts +36 -0
  343. package/src/composables/controlWorkspaceOptions.ts +21 -0
  344. package/src/composables/formBuilderSchema.ts +153 -0
  345. package/src/composables/pluginEndpointBuilder.ts +203 -0
  346. package/src/composables/protocolTemplateCatalog.ts +325 -0
  347. package/src/composables/useAutoGroup.ts +395 -549
  348. package/src/composables/useAutoGroupInputSources.ts +147 -0
  349. package/src/composables/useBioTemplateControls.ts +1 -1
  350. package/src/composables/useBioTemplatePresetWorkspace.ts +1 -1
  351. package/src/composables/useBioTemplateWorkspace.ts +1 -1
  352. package/src/composables/useControlSchema.ts +64 -1312
  353. package/src/composables/useControlWorkspace.ts +201 -0
  354. package/src/composables/useForm.ts +5 -187
  355. package/src/composables/useFormBuilder.ts +11 -153
  356. package/src/composables/useFormValidation.ts +154 -0
  357. package/src/composables/usePluginClient.ts +10 -193
  358. package/src/composables/useProtocolTemplates.ts +10 -328
  359. package/src/composables/useScheduleCalendarLayout.ts +287 -0
  360. package/src/styles/components/auto-group-modal.css +248 -310
  361. package/src/templates/adapters.ts +89 -930
  362. package/src/templates/assayLookups.ts +33 -0
  363. package/src/templates/assayMatrixAdapters.ts +78 -0
  364. package/src/templates/assayMatrixBuilder.ts +59 -0
  365. package/src/templates/assayNormalizers.ts +34 -0
  366. package/src/templates/builderDefaults.ts +11 -0
  367. package/src/templates/builderIdUtils.ts +20 -0
  368. package/src/templates/builderPresetControls.ts +165 -0
  369. package/src/templates/builderReadUtils.ts +57 -0
  370. package/src/templates/builders.ts +122 -2350
  371. package/src/templates/calibrationCurveAdapters.ts +59 -0
  372. package/src/templates/calibrationCurveBuilder.ts +99 -0
  373. package/src/templates/calibrationNormalizers.ts +60 -0
  374. package/src/templates/componentBindingCatalog.ts +90 -0
  375. package/src/templates/componentBindingHelpers.ts +93 -0
  376. package/src/templates/componentBindings.ts +12 -461
  377. package/src/templates/componentDoseResponseProps.ts +42 -0
  378. package/src/templates/componentGenericProps.ts +77 -0
  379. package/src/templates/componentPlateHelpers.ts +29 -0
  380. package/src/templates/componentPlateMapProps.ts +32 -0
  381. package/src/templates/componentPropsFactory.ts +21 -0
  382. package/src/templates/componentQpcrPlateProps.ts +28 -0
  383. package/src/templates/componentTargetResolvers.ts +69 -0
  384. package/src/templates/componentTemplateProps.ts +78 -0
  385. package/src/templates/controlSchemaClone.ts +32 -0
  386. package/src/templates/controlSchemaConstants.ts +11 -0
  387. package/src/templates/controlSchemaMerge.ts +40 -0
  388. package/src/templates/controlSchemaTargets.ts +87 -0
  389. package/src/templates/controlSchemaTypes.ts +20 -0
  390. package/src/templates/controlSchemas.ts +22 -704
  391. package/src/templates/defaultBioTemplateBuilder.ts +124 -0
  392. package/src/templates/doseResponseAdapters.ts +45 -0
  393. package/src/templates/doseResponseBuilder.ts +44 -0
  394. package/src/templates/elisaAssayCollectionBuilder.ts +62 -0
  395. package/src/templates/flowCytometryAssayCollectionBuilder.ts +41 -0
  396. package/src/templates/flowCytometryPanelBuilder.ts +53 -0
  397. package/src/templates/flowNormalizers.ts +58 -0
  398. package/src/templates/flowPanelAdapters.ts +58 -0
  399. package/src/templates/instrumentRunAdapterHelpers.ts +94 -0
  400. package/src/templates/instrumentRunAdapters.ts +163 -0
  401. package/src/templates/instrumentRunBuilder.ts +97 -0
  402. package/src/templates/lcmsBatchCollectionBuilder.ts +38 -0
  403. package/src/templates/plateGeometry.ts +62 -0
  404. package/src/templates/plateMapAdapters.ts +36 -0
  405. package/src/templates/plateMapBuilder.ts +43 -0
  406. package/src/templates/presetControlSchemas.ts +258 -0
  407. package/src/templates/protocolAdapters.ts +69 -0
  408. package/src/templates/protocolNormalizers.ts +37 -0
  409. package/src/templates/protocolStepsBuilder.ts +36 -0
  410. package/src/templates/qpcrAdapters.ts +104 -0
  411. package/src/templates/qpcrExpressionCollectionBuilder.ts +33 -0
  412. package/src/templates/qpcrPlateBuilder.ts +96 -0
  413. package/src/templates/reagentAdapters.ts +77 -0
  414. package/src/templates/reagentListBuilder.ts +30 -0
  415. package/src/templates/runNormalizers.ts +63 -0
  416. package/src/templates/sampleNormalizers.ts +58 -0
  417. package/src/templates/samplePrepAdapters.ts +63 -0
  418. package/src/templates/samplePrepBuilder.ts +51 -0
  419. package/src/templates/sampleSheetAdapters.ts +75 -0
  420. package/src/templates/sampleSheetBuilder.ts +23 -0
  421. package/src/templates/targetedMetabolomicsCollectionBuilder.ts +79 -0
  422. package/src/templates/targetedMetabolomicsHelpers.ts +102 -0
  423. package/src/templates/templateAdapterTypes.ts +58 -0
  424. package/src/templates/templateControlSchemas.ts +320 -0
  425. package/src/templates/templateCreateOptions.ts +208 -0
  426. package/src/templates/templateEnvelopes.ts +137 -0
  427. package/src/templates/templatePackCollectionBuilder.ts +23 -0
  428. package/src/templates/templatePresetCollectionBuilder.ts +139 -0
  429. package/src/templates/templateQpcrTypes.ts +48 -0
  430. package/src/templates/templateValidators.ts +414 -0
  431. package/src/templates/timeCourseAdapters.ts +73 -0
  432. package/src/templates/timeCourseBuilder.ts +64 -0
  433. package/src/templates/types.ts +79 -275
  434. package/src/templates/wellPlateScreenCollectionBuilder.ts +36 -0
  435. package/src/templates/westernBlotAssayCollectionBuilder.ts +68 -0
  436. package/src/types/auto-group.ts +107 -9
  437. package/src/types/componentLabTypes.ts +235 -0
  438. package/src/types/componentWorkflowTypes.ts +190 -0
  439. package/src/types/components.ts +74 -424
  440. package/dist/components-BhK-dW99.js.map +0 -1
  441. package/dist/composables-Bg7CFuNz.js.map +0 -1
  442. package/dist/templates-BorLR_7p.js.map +0 -1
  443. package/dist/useProtocolTemplates-n6AJqSqv.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { Bt as ensureTemplateFromCollection, In as useControlWorkspace, Ln as getFieldRegistryEntry, Pt as createTemplateCollection, Rn as getTypeDefault, Vn as useConcentrationUnits, Vt as extractTemplateCollection, _ as toBioTemplateComponentPropsByComponent$1, _t as createBioTemplatePresetCollectionFromControls, b as toBioTemplateComponentUsage, d as getBioTemplateComponentProps$1, g as toBioTemplateComponentProps, gt as createBioTemplatePresetCollection, h as toBioTemplateComponentImports, ht as createBioTemplatePackCollection, i as createBioTemplateControlToolkit, m as toBioTemplateComponentBindingsById, nn as getBioTemplatePresetInfo, on as getBioTemplatePackInfo, p as toBioTemplateComponentBindings, u as getBioTemplateComponentBindings, v as toBioTemplateComponentPropsById, y as toBioTemplateComponentSnippets } from "./templates-BorLR_7p.js";
1
+ import { B as toBioTemplateComponentProps, F as getBioTemplateComponentProps$1, H as toBioTemplateComponentPropsById, Hn as useConcentrationUnits, Jt as getBioTemplatePresetInfo, L as toBioTemplateComponentBindings, P as getBioTemplateComponentBindings, Qt as getBioTemplatePackInfo, R as toBioTemplateComponentBindingsById, Rn as getFieldRegistryEntry, Sn as useControlWorkspace, U as toBioTemplateComponentSnippets, V as toBioTemplateComponentPropsByComponent$1, W as toBioTemplateComponentUsage, d as createBioTemplatePresetCollectionFromControls, dn as extractTemplateCollection, i as createBioTemplateControlToolkit, p as createBioTemplatePackCollection, sn as createTemplateCollection, u as createBioTemplatePresetCollection, un as ensureTemplateFromCollection, z as toBioTemplateComponentImports, zn as getTypeDefault } from "./templates-DtdUvJ4c.js";
2
2
  import { g as useSettingsStore, t as useAuthStore } from "./auth-B7g4J4ZF.js";
3
3
  import { computed, effectScope, getCurrentScope, inject, onMounted, onScopeDispose, onUnmounted, provide, reactive, readonly, ref, shallowRef, toRaw, toValue, watch } from "vue";
4
4
  import axios from "axios";
@@ -144,7 +144,7 @@ function useTheme() {
144
144
  };
145
145
  }
146
146
  //#endregion
147
- //#region src/composables/useForm.ts
147
+ //#region src/composables/useFormValidation.ts
148
148
  var validators = {
149
149
  required: (value, message = "This field is required") => {
150
150
  if (value === null || value === void 0 || value === "") return message;
@@ -182,6 +182,70 @@ var validators = {
182
182
  return null;
183
183
  }
184
184
  };
185
+ function validateFieldValue(value, fieldRules, formData) {
186
+ if (!fieldRules) return null;
187
+ if (fieldRules.required) {
188
+ const message = typeof fieldRules.required === "string" ? fieldRules.required : void 0;
189
+ const error = validators.required(value, message);
190
+ if (error) return error;
191
+ }
192
+ if (value === null || value === void 0 || value === "") return null;
193
+ if (fieldRules.minLength !== void 0) {
194
+ const config = typeof fieldRules.minLength === "number" ? {
195
+ value: fieldRules.minLength,
196
+ message: void 0
197
+ } : fieldRules.minLength;
198
+ const error = validators.minLength(value, config.value, config.message);
199
+ if (error) return error;
200
+ }
201
+ if (fieldRules.maxLength !== void 0) {
202
+ const config = typeof fieldRules.maxLength === "number" ? {
203
+ value: fieldRules.maxLength,
204
+ message: void 0
205
+ } : fieldRules.maxLength;
206
+ const error = validators.maxLength(value, config.value, config.message);
207
+ if (error) return error;
208
+ }
209
+ if (fieldRules.min !== void 0) {
210
+ const config = typeof fieldRules.min === "number" ? {
211
+ value: fieldRules.min,
212
+ message: void 0
213
+ } : fieldRules.min;
214
+ const error = validators.min(value, config.value, config.message);
215
+ if (error) return error;
216
+ }
217
+ if (fieldRules.max !== void 0) {
218
+ const config = typeof fieldRules.max === "number" ? {
219
+ value: fieldRules.max,
220
+ message: void 0
221
+ } : fieldRules.max;
222
+ const error = validators.max(value, config.value, config.message);
223
+ if (error) return error;
224
+ }
225
+ if (fieldRules.pattern !== void 0) {
226
+ const config = fieldRules.pattern instanceof RegExp ? {
227
+ value: fieldRules.pattern,
228
+ message: void 0
229
+ } : fieldRules.pattern;
230
+ const error = validators.pattern(value, config.value, config.message);
231
+ if (error) return error;
232
+ }
233
+ if (fieldRules.email) {
234
+ const message = typeof fieldRules.email === "string" ? fieldRules.email : void 0;
235
+ const error = validators.email(value, message);
236
+ if (error) return error;
237
+ }
238
+ if (fieldRules.custom) {
239
+ const customRules = Array.isArray(fieldRules.custom) ? fieldRules.custom : [fieldRules.custom];
240
+ for (const rule of customRules) {
241
+ const error = rule(value, formData);
242
+ if (error) return error;
243
+ }
244
+ }
245
+ return null;
246
+ }
247
+ //#endregion
248
+ //#region src/composables/useForm.ts
185
249
  /**
186
250
  * Form state management composable with validation.
187
251
  *
@@ -228,97 +292,9 @@ function useForm(initialValues, rules = {}) {
228
292
  function validateField(field) {
229
293
  const value = data[field];
230
294
  const fieldRules = rules[field];
231
- if (!fieldRules) {
232
- errors[field] = null;
233
- return true;
234
- }
235
- if (fieldRules.required) {
236
- const message = typeof fieldRules.required === "string" ? fieldRules.required : void 0;
237
- const error = validators.required(value, message);
238
- if (error) {
239
- errors[field] = error;
240
- return false;
241
- }
242
- }
243
- if (value === null || value === void 0 || value === "") {
244
- errors[field] = null;
245
- return true;
246
- }
247
- if (fieldRules.minLength !== void 0) {
248
- const config = typeof fieldRules.minLength === "number" ? {
249
- value: fieldRules.minLength,
250
- message: void 0
251
- } : fieldRules.minLength;
252
- const error = validators.minLength(value, config.value, config.message);
253
- if (error) {
254
- errors[field] = error;
255
- return false;
256
- }
257
- }
258
- if (fieldRules.maxLength !== void 0) {
259
- const config = typeof fieldRules.maxLength === "number" ? {
260
- value: fieldRules.maxLength,
261
- message: void 0
262
- } : fieldRules.maxLength;
263
- const error = validators.maxLength(value, config.value, config.message);
264
- if (error) {
265
- errors[field] = error;
266
- return false;
267
- }
268
- }
269
- if (fieldRules.min !== void 0) {
270
- const config = typeof fieldRules.min === "number" ? {
271
- value: fieldRules.min,
272
- message: void 0
273
- } : fieldRules.min;
274
- const error = validators.min(value, config.value, config.message);
275
- if (error) {
276
- errors[field] = error;
277
- return false;
278
- }
279
- }
280
- if (fieldRules.max !== void 0) {
281
- const config = typeof fieldRules.max === "number" ? {
282
- value: fieldRules.max,
283
- message: void 0
284
- } : fieldRules.max;
285
- const error = validators.max(value, config.value, config.message);
286
- if (error) {
287
- errors[field] = error;
288
- return false;
289
- }
290
- }
291
- if (fieldRules.pattern !== void 0) {
292
- const config = fieldRules.pattern instanceof RegExp ? {
293
- value: fieldRules.pattern,
294
- message: void 0
295
- } : fieldRules.pattern;
296
- const error = validators.pattern(value, config.value, config.message);
297
- if (error) {
298
- errors[field] = error;
299
- return false;
300
- }
301
- }
302
- if (fieldRules.email) {
303
- const message = typeof fieldRules.email === "string" ? fieldRules.email : void 0;
304
- const error = validators.email(value, message);
305
- if (error) {
306
- errors[field] = error;
307
- return false;
308
- }
309
- }
310
- if (fieldRules.custom) {
311
- const customRules = Array.isArray(fieldRules.custom) ? fieldRules.custom : [fieldRules.custom];
312
- for (const rule of customRules) {
313
- const error = rule(value, data);
314
- if (error) {
315
- errors[field] = error;
316
- return false;
317
- }
318
- }
319
- }
320
- errors[field] = null;
321
- return true;
295
+ const error = validateFieldValue(value, fieldRules, data);
296
+ errors[field] = error;
297
+ return error === null;
322
298
  }
323
299
  function validate() {
324
300
  let isAllValid = true;
@@ -422,7 +398,7 @@ function isPlainRecord(value) {
422
398
  return Object.prototype.toString.call(value) === "[object Object]";
423
399
  }
424
400
  //#endregion
425
- //#region src/composables/useFormBuilder.ts
401
+ //#region src/composables/formBuilderSchema.ts
426
402
  /**
427
403
  * Evaluate a JSON-serializable field condition against the current form data.
428
404
  *
@@ -510,6 +486,8 @@ function replaceRecord(target, source) {
510
486
  function hasOwnKey(source, key) {
511
487
  return Object.prototype.hasOwnProperty.call(source, key);
512
488
  }
489
+ //#endregion
490
+ //#region src/composables/useFormBuilder.ts
513
491
  /**
514
492
  * Drive a `FormSchema` as reactive form state.
515
493
  *
@@ -2098,6 +2076,788 @@ function useWellPlateEditor(initialState, options = {}) {
2098
2076
  };
2099
2077
  }
2100
2078
  //#endregion
2079
+ //#region src/composables/autoGroup/tokenize.ts
2080
+ var DELIMITER_CANDIDATES = [
2081
+ "_",
2082
+ "-",
2083
+ "."
2084
+ ];
2085
+ /** Pick the delimiter whose split produces the most consistent field count across `lines`. */
2086
+ function pickPrimaryDelimiter(lines) {
2087
+ if (lines.length === 0) return "_";
2088
+ let best = "_";
2089
+ let bestScore = -1;
2090
+ for (const candidate of DELIMITER_CANDIDATES) {
2091
+ const counts = lines.map((l) => l.split(candidate).length);
2092
+ const frequency = /* @__PURE__ */ new Map();
2093
+ for (const c of counts) frequency.set(c, (frequency.get(c) ?? 0) + 1);
2094
+ let mode = 1;
2095
+ let modeFreq = 0;
2096
+ for (const [c, f] of frequency) if (f > modeFreq || f === modeFreq && c > mode) {
2097
+ mode = c;
2098
+ modeFreq = f;
2099
+ }
2100
+ const score = mode > 1 ? modeFreq / lines.length : 0;
2101
+ if (score > bestScore) {
2102
+ best = candidate;
2103
+ bestScore = score;
2104
+ }
2105
+ }
2106
+ return best;
2107
+ }
2108
+ /** Split a single sample name on `delimiter`. Empty input returns `['']` so callers
2109
+ * always receive at least one token. */
2110
+ function splitMulti(name, delimiter) {
2111
+ return name.split(delimiter);
2112
+ }
2113
+ //#endregion
2114
+ //#region src/composables/autoGroup/classKey.ts
2115
+ function classKey(c) {
2116
+ return c.subKind ? `${c.kind}:${c.subKind}` : c.kind;
2117
+ }
2118
+ var vocab_default = {
2119
+ $schema: "./vocab.schema.json",
2120
+ _doc: {
2121
+ "purpose": "Sample-name classification vocabulary for the auto-group pipeline. Edit this file to teach the classifier new sample types or matrices without changing TypeScript. All matching is case-insensitive unless a pattern's regex flags say otherwise.",
2122
+ "patterns": "Regex source strings (no surrounding slashes). Compiled with the `i` flag at load time.",
2123
+ "standardAllow": "Tokens that should be classified as Standard even when they don't match `patterns.standard`. Case-sensitive — use the exact spelling that appears in your filenames.",
2124
+ "matrixVocab": "Lowercase token → canonical display name. Tokens are normalised to lowercase before lookup, so adding 'tissues' handles 'Tissues' / 'TISSUES' / 'tissues' uniformly. Multiple aliases map to one canonical (e.g. cell + cells → Cells).",
2125
+ "ionizationModes": "Lowercase polarity tokens (pos / positive / neg / negative). Suffixed to the subKind so POS and NEG acquisitions get independent schemas.",
2126
+ "tissueParentTokens": "Tokens that flag the immediately-following token as the organ subKind (so `tissues / kidney` becomes Biological/Tissues, tagging both positions).",
2127
+ "replicateStripPatterns": "Regex sources stripped from each sample name during the replicate pre-grouping pass. Samples whose stripped (base) names match are treated as replicates of one another and grouped together before tokenisation. Default set covers: trailing run-order numbers, _T<n>, _B<n>, and _Rep<n> markers."
2128
+ },
2129
+ patterns: {
2130
+ "blank": "^(blank|blk|solv?blank|matrix.?blank)$",
2131
+ "iqc": "^iqc$",
2132
+ "eqc": "^eqc$",
2133
+ "standard": "^(standard|std\\d*|cal(?:ibrator)?\\d*|.+standard)$",
2134
+ "spikeIn": "spike.?in",
2135
+ "stock": "^stock$"
2136
+ },
2137
+ standardAllow: [
2138
+ "ExtractionS",
2139
+ "IS",
2140
+ "internal.standard",
2141
+ "PQC",
2142
+ "pooledQC"
2143
+ ],
2144
+ matrixVocab: {
2145
+ "plasma": "Plasma",
2146
+ "serum": "Serum",
2147
+ "tissue": "Tissue",
2148
+ "tissues": "Tissues",
2149
+ "kidney": "Kidney",
2150
+ "liver": "Liver",
2151
+ "tumor": "Tumor",
2152
+ "urine": "Urine",
2153
+ "brain": "Brain",
2154
+ "muscle": "Muscle",
2155
+ "adipose": "Adipose",
2156
+ "csf": "CSF",
2157
+ "feces": "Feces",
2158
+ "faeces": "Faeces",
2159
+ "cells": "Cells",
2160
+ "cell": "Cells",
2161
+ "media": "Media",
2162
+ "medium": "Medium",
2163
+ "extract": "Extract"
2164
+ },
2165
+ ionizationModes: {
2166
+ "pos": "POS",
2167
+ "positive": "POS",
2168
+ "neg": "NEG",
2169
+ "negative": "NEG"
2170
+ },
2171
+ tissueParentTokens: ["tissue", "tissues"],
2172
+ replicateStripPatterns: [
2173
+ "[_-]\\d{2,4}[A-Za-z]?$",
2174
+ "[_-]T\\d+(?=[_-]|$)",
2175
+ "[_-]B\\d+(?=[_-]|$)",
2176
+ "[_-](?:rep(?:licate)?)\\d+(?=[_-]|$)"
2177
+ ]
2178
+ };
2179
+ //#endregion
2180
+ //#region src/composables/autoGroup/classify.ts
2181
+ function compileVocab(data) {
2182
+ return {
2183
+ blankPattern: new RegExp(data.patterns.blank, "i"),
2184
+ iqcPattern: new RegExp(data.patterns.iqc, "i"),
2185
+ eqcPattern: new RegExp(data.patterns.eqc, "i"),
2186
+ standardPattern: new RegExp(data.patterns.standard, "i"),
2187
+ spikeInPattern: new RegExp(data.patterns.spikeIn, "i"),
2188
+ stockPattern: new RegExp(data.patterns.stock, "i"),
2189
+ standardAllow: new Set(data.standardAllow),
2190
+ matrixVocab: new Map(Object.entries(data.matrixVocab)),
2191
+ ionizationModes: new Map(Object.entries(data.ionizationModes).map(([k, v]) => [k, v])),
2192
+ tissueParentTokens: new Set(data.tissueParentTokens)
2193
+ };
2194
+ }
2195
+ var classVocab = compileVocab(vocab_default);
2196
+ function matrixCanonical(token, vocab) {
2197
+ return vocab.matrixVocab.get(token.toLowerCase());
2198
+ }
2199
+ function isTissueParentToken(token, vocab) {
2200
+ return vocab.tissueParentTokens.has(token.toLowerCase());
2201
+ }
2202
+ var DEFAULT_DISPOSITION = {
2203
+ biological: "group",
2204
+ iqc: "overlay",
2205
+ eqc: "overlay",
2206
+ standard: "overlay",
2207
+ "spike-in": "overlay",
2208
+ stock: "overlay",
2209
+ blank: "overlay",
2210
+ unknown: "group"
2211
+ };
2212
+ function classifyTokens(tokens, vocab) {
2213
+ for (let i = 0; i < tokens.length; i++) if (vocab.blankPattern.test(tokens[i])) return {
2214
+ kind: "blank",
2215
+ tagPositions: [i]
2216
+ };
2217
+ for (let i = 0; i < tokens.length; i++) if (vocab.iqcPattern.test(tokens[i])) return {
2218
+ kind: "iqc",
2219
+ tagPositions: [i]
2220
+ };
2221
+ for (let i = 0; i < tokens.length; i++) if (vocab.eqcPattern.test(tokens[i])) return {
2222
+ kind: "eqc",
2223
+ tagPositions: [i]
2224
+ };
2225
+ for (let i = 0; i < tokens.length; i++) if (vocab.standardPattern.test(tokens[i]) || vocab.standardAllow.has(tokens[i])) return {
2226
+ kind: "standard",
2227
+ tagPositions: [i]
2228
+ };
2229
+ for (let i = 0; i < tokens.length; i++) if (vocab.spikeInPattern.test(tokens[i])) return {
2230
+ kind: "spike-in",
2231
+ tagPositions: [i]
2232
+ };
2233
+ for (let i = 0; i < tokens.length; i++) if (vocab.stockPattern.test(tokens[i])) return {
2234
+ kind: "stock",
2235
+ tagPositions: [i]
2236
+ };
2237
+ for (let i = 0; i < tokens.length; i++) {
2238
+ const canonical = matrixCanonical(tokens[i], vocab);
2239
+ if (canonical) {
2240
+ const isTissueParent = isTissueParentToken(tokens[i], vocab);
2241
+ const tagPositions = [i];
2242
+ if (isTissueParent) {
2243
+ if (i + 1 < tokens.length) tagPositions.push(i + 1);
2244
+ return {
2245
+ kind: "biological",
2246
+ subKind: "Tissues",
2247
+ tagPositions
2248
+ };
2249
+ }
2250
+ if (i > 0 && isTissueParentToken(tokens[i - 1], vocab)) {
2251
+ tagPositions.unshift(i - 1);
2252
+ return {
2253
+ kind: "biological",
2254
+ subKind: "Tissues",
2255
+ tagPositions
2256
+ };
2257
+ }
2258
+ return {
2259
+ kind: "biological",
2260
+ subKind: canonical,
2261
+ tagPositions
2262
+ };
2263
+ }
2264
+ }
2265
+ return {
2266
+ kind: "unknown",
2267
+ tagPositions: []
2268
+ };
2269
+ }
2270
+ /**
2271
+ * LC-MS batches run positive- and negative-ion-mode acquisitions separately,
2272
+ * and the two produce different feature tables that should never be grouped
2273
+ * together. When a POS / NEG / positive / negative token is present we suffix
2274
+ * it to the class label so the schema builder treats each mode as its own class.
2275
+ */
2276
+ function ionizationModeFor(tokens, vocab) {
2277
+ for (let i = 0; i < tokens.length; i++) {
2278
+ const mode = vocab.ionizationModes.get(tokens[i].toLowerCase());
2279
+ if (mode) return {
2280
+ mode,
2281
+ position: i
2282
+ };
2283
+ }
2284
+ }
2285
+ function labelFor(kind, subKind) {
2286
+ const base = {
2287
+ biological: "Biological",
2288
+ iqc: "IQC",
2289
+ eqc: "EQC",
2290
+ standard: "Standard",
2291
+ "spike-in": "SpikeIn",
2292
+ stock: "Stock",
2293
+ blank: "Blank",
2294
+ unknown: "Unknown"
2295
+ };
2296
+ return subKind ? `${base[kind]} / ${subKind}` : base[kind];
2297
+ }
2298
+ /** Classify each tokenized sample into a SampleClass. */
2299
+ function detectClass(tokenizedSamples, options) {
2300
+ const vocab = {
2301
+ ...classVocab,
2302
+ ...options.vocab ?? {}
2303
+ };
2304
+ const hints = options.sampleTypeHints ?? [];
2305
+ const buckets = /* @__PURE__ */ new Map();
2306
+ for (let i = 0; i < tokenizedSamples.length; i++) {
2307
+ const hint = hints[i]?.toLowerCase();
2308
+ let kind;
2309
+ let subKind;
2310
+ let tagPositions;
2311
+ if (hint === "qc") {
2312
+ kind = "iqc";
2313
+ tagPositions = [];
2314
+ } else if (hint === "blank") {
2315
+ kind = "blank";
2316
+ tagPositions = [];
2317
+ } else {
2318
+ const r = classifyTokens(tokenizedSamples[i], vocab);
2319
+ kind = r.kind;
2320
+ subKind = r.subKind;
2321
+ tagPositions = r.tagPositions;
2322
+ }
2323
+ const mode = ionizationModeFor(tokenizedSamples[i], vocab);
2324
+ if (mode) {
2325
+ subKind = subKind ? `${subKind} / ${mode.mode}` : mode.mode;
2326
+ if (!tagPositions.includes(mode.position)) tagPositions.push(mode.position);
2327
+ }
2328
+ const key = classKey({
2329
+ kind,
2330
+ subKind
2331
+ });
2332
+ const bucket = buckets.get(key);
2333
+ if (bucket) {
2334
+ bucket.members.push(i);
2335
+ for (const p of tagPositions) if (!bucket.classTagPositions.includes(p)) bucket.classTagPositions.push(p);
2336
+ } else buckets.set(key, {
2337
+ kind,
2338
+ subKind,
2339
+ label: labelFor(kind, subKind),
2340
+ members: [i],
2341
+ classTagPositions: [...tagPositions],
2342
+ disposition: DEFAULT_DISPOSITION[kind]
2343
+ });
2344
+ }
2345
+ return [...buckets.values()];
2346
+ }
2347
+ //#endregion
2348
+ //#region src/composables/autoGroup/columns.ts
2349
+ var NUMERIC_VALUE_UNIT = /^(\d+(?:dot\d+)?)([A-Za-z%/μ]+)$/;
2350
+ var RATIO = /^(\d+)to(\d+)$/;
2351
+ var RUN_ORDER_PATTERN = /^\d{2,4}[A-Z]?$/;
2352
+ var REPLICATE_TOKEN_PATTERN = /^(?:rep(?:licate)?|r)\d+$/i;
2353
+ var DEFAULT_FACTOR_CARDINALITY_MAX = 50;
2354
+ var LOW_CARDINALITY_NUMERIC_AS_FACTOR_MAX = 5;
2355
+ function parseNumeric(token) {
2356
+ const ratio = RATIO.exec(token);
2357
+ if (ratio) {
2358
+ const num = Number(ratio[1]);
2359
+ const den = Number(ratio[2]);
2360
+ if (den === 0) return null;
2361
+ return {
2362
+ value: num / den,
2363
+ unit: "ratio",
2364
+ raw: token
2365
+ };
2366
+ }
2367
+ const m = NUMERIC_VALUE_UNIT.exec(token);
2368
+ if (!m) return null;
2369
+ const valueStr = m[1].replace(/dot/g, ".");
2370
+ const value = Number(valueStr);
2371
+ if (Number.isNaN(value)) return null;
2372
+ return {
2373
+ value,
2374
+ unit: m[2],
2375
+ raw: token
2376
+ };
2377
+ }
2378
+ function inferRole(input) {
2379
+ const { values, position, totalPositions, classSize } = input;
2380
+ const cardinality = new Set(values).size;
2381
+ if (cardinality === 1) return "constant";
2382
+ if (position === totalPositions - 1) {
2383
+ if (values.every((v) => RUN_ORDER_PATTERN.test(v)) && cardinality >= classSize * .5) return "run-order";
2384
+ }
2385
+ if (values.every((v) => parseNumeric(v) !== null)) {
2386
+ if (cardinality <= LOW_CARDINALITY_NUMERIC_AS_FACTOR_MAX) return "factor";
2387
+ return "numeric";
2388
+ }
2389
+ if (values.every((v) => REPLICATE_TOKEN_PATTERN.test(v))) return "replicate";
2390
+ if (cardinality <= DEFAULT_FACTOR_CARDINALITY_MAX) return "factor";
2391
+ return "ignore";
2392
+ }
2393
+ /**
2394
+ * Auto-detect a friendlier column name from the column's values + role.
2395
+ * Returns null when nothing matches, so callers can fall back to "Token N".
2396
+ *
2397
+ * Heuristics are content-derived and deterministic. They cover the common
2398
+ * LC-MS / proteomics naming conventions; users can override anything via
2399
+ * the column popover (rename input).
2400
+ */
2401
+ function inferColumnName(values, uniqueValues, role) {
2402
+ if (uniqueValues.length === 0) return null;
2403
+ if ((role === "numeric" || role === "factor") && values.every((v) => parseNumeric(v) !== null)) {
2404
+ const categoryFor = (u) => {
2405
+ const ul = u.toLowerCase();
2406
+ if ([
2407
+ "min",
2408
+ "sec",
2409
+ "s",
2410
+ "hr",
2411
+ "h",
2412
+ "d",
2413
+ "day",
2414
+ "days",
2415
+ "wk",
2416
+ "week",
2417
+ "hour",
2418
+ "hours"
2419
+ ].includes(ul)) return "Timepoint";
2420
+ if ([
2421
+ "nm",
2422
+ "um",
2423
+ "mm",
2424
+ "m",
2425
+ "molar",
2426
+ "mol"
2427
+ ].includes(ul)) return "Concentration";
2428
+ if ([
2429
+ "mg",
2430
+ "g",
2431
+ "kg",
2432
+ "ng",
2433
+ "ug",
2434
+ "pg"
2435
+ ].includes(ul)) return "Mass";
2436
+ if ([
2437
+ "ml",
2438
+ "l",
2439
+ "ul",
2440
+ "nl",
2441
+ "pl"
2442
+ ].includes(ul)) return "Volume";
2443
+ if (ul === "%") return "Percentage";
2444
+ if ([
2445
+ "c",
2446
+ "k",
2447
+ "celsius",
2448
+ "kelvin"
2449
+ ].includes(ul)) return "Temperature";
2450
+ return null;
2451
+ };
2452
+ const tally = /* @__PURE__ */ new Map();
2453
+ for (const v of values) {
2454
+ const p = parseNumeric(v);
2455
+ const cat = p ? categoryFor(p.unit) : null;
2456
+ if (cat) tally.set(cat, (tally.get(cat) ?? 0) + 1);
2457
+ }
2458
+ let best = null;
2459
+ for (const [cat, count] of tally) if (!best || count > best.count) best = {
2460
+ cat,
2461
+ count
2462
+ };
2463
+ if (best && best.count >= values.length / 2) return best.cat;
2464
+ }
2465
+ if (values.every((v) => /^\d{6}$/.test(v) || /^\d{8}$/.test(v))) return "Date";
2466
+ if (role === "run-order") return "Run Order";
2467
+ if (values.every((v) => /^(pos|neg|positive|negative)$/i.test(v))) return "Polarity";
2468
+ if (values.every((v) => /^(plasma|serum|tissue|tissues|kidney|liver|tumor|urine|brain|muscle|adipose|csf|feces|faeces|cells?|media|medium|extract)$/i.test(v))) return "Matrix";
2469
+ if (values.every((v) => /^(exp|expt|study)\d+$/i.test(v))) return "Experiment";
2470
+ if (uniqueValues.length <= 4 && values.every((v) => /^[A-Z]{2,3}$/.test(v))) return "Username";
2471
+ return null;
2472
+ }
2473
+ function buildClassSchema(tokenizedMembers, classKind, subKind, classTagPositions) {
2474
+ const totalPositions = tokenizedMembers[0]?.length ?? 0;
2475
+ const classSize = tokenizedMembers.length;
2476
+ const columns = [];
2477
+ const tagSet = new Set(classTagPositions);
2478
+ for (let pos = 0; pos < totalPositions; pos++) {
2479
+ const values = tokenizedMembers.map((t) => t[pos] ?? "");
2480
+ const uniqueValues = [...new Set(values)];
2481
+ let role;
2482
+ if (tagSet.has(pos) && uniqueValues.length === 1) role = "class-tag";
2483
+ else role = inferRole({
2484
+ values,
2485
+ position: pos,
2486
+ totalPositions,
2487
+ classSize
2488
+ });
2489
+ const numeric = role === "numeric" ? { byValue: Object.fromEntries(uniqueValues.map((v) => [v, parseNumeric(v)]).filter((e) => e[1] !== null)) } : void 0;
2490
+ const inferredName = inferColumnName(values, uniqueValues, role);
2491
+ columns.push({
2492
+ index: pos,
2493
+ name: inferredName ?? `Token ${pos + 1}`,
2494
+ sourceIndices: [pos],
2495
+ uniqueValues,
2496
+ cardinality: uniqueValues.length,
2497
+ role,
2498
+ numeric,
2499
+ type: pos === 0 ? "prefix" : "suffix"
2500
+ });
2501
+ }
2502
+ let usernameAssigned = false;
2503
+ for (const c of columns) if (c.name === "Username") if (usernameAssigned) c.name = "Project";
2504
+ else usernameAssigned = true;
2505
+ return {
2506
+ classKind,
2507
+ subKind,
2508
+ columns,
2509
+ groupBy: columns.filter((c) => c.role === "factor").map((c) => c.index)
2510
+ };
2511
+ }
2512
+ //#endregion
2513
+ //#region src/composables/autoGroup/cooccurrence.ts
2514
+ var THRESHOLD = .95;
2515
+ var MIN_CLASS_SIZE = 4;
2516
+ function functionalDependency(rows, srcIdx, dstIdx) {
2517
+ const map = /* @__PURE__ */ new Map();
2518
+ for (const row of rows) {
2519
+ const a = row[srcIdx] ?? "";
2520
+ const b = row[dstIdx] ?? "";
2521
+ const set = map.get(a);
2522
+ if (set) set.add(b);
2523
+ else map.set(a, new Set([b]));
2524
+ }
2525
+ if (map.size === 0) return 0;
2526
+ let functional = 0;
2527
+ for (const set of map.values()) if (set.size === 1) functional++;
2528
+ return functional / map.size;
2529
+ }
2530
+ function findMerges(schema, rows) {
2531
+ if (rows.length < MIN_CLASS_SIZE) return [];
2532
+ const out = [];
2533
+ for (let i = 0; i < schema.columns.length - 1; i++) {
2534
+ const a = schema.columns[i];
2535
+ const b = schema.columns[i + 1];
2536
+ if (a.role !== "factor" && b.role !== "factor") continue;
2537
+ const forward = functionalDependency(rows, a.index, b.index);
2538
+ const backward = functionalDependency(rows, b.index, a.index);
2539
+ if (forward < THRESHOLD || backward < THRESHOLD) continue;
2540
+ if (new Set(rows.map((r) => `${r[a.index] ?? ""}_${r[b.index] ?? ""}`)).size < 2) continue;
2541
+ out.push({
2542
+ classKind: schema.classKind,
2543
+ subKind: schema.subKind,
2544
+ columnIndices: [a.index, b.index],
2545
+ proposedName: `Column ${a.index + 1}–${b.index + 1}`,
2546
+ confidence: Math.min(forward, backward)
2547
+ });
2548
+ }
2549
+ return out;
2550
+ }
2551
+ //#endregion
2552
+ //#region src/composables/autoGroup/colors.ts
2553
+ var DEFAULT_COLORS = [
2554
+ "#3B82F6",
2555
+ "#10B981",
2556
+ "#F59E0B",
2557
+ "#EF4444",
2558
+ "#8B5CF6",
2559
+ "#EC4899",
2560
+ "#06B6D4",
2561
+ "#84CC16",
2562
+ "#F97316",
2563
+ "#6366F1"
2564
+ ];
2565
+ var QC_OVERLAY_COLOR = "#6B7280";
2566
+ //#endregion
2567
+ //#region src/composables/autoGroup/compose.ts
2568
+ /** Apply the operation pipeline to a single raw token value:
2569
+ * numeric-parse → alias → exclude → bin
2570
+ * Returns null if the row is excluded.
2571
+ */
2572
+ function applyOps(col, raw) {
2573
+ let value = raw;
2574
+ if (col.ops?.alias && raw in col.ops.alias) value = col.ops.alias[raw];
2575
+ if (col.ops?.exclude?.includes(raw) || col.ops?.exclude?.includes(value)) return null;
2576
+ if (col.role === "numeric" && col.binning?.mode === "binned") {
2577
+ const parsed = col.numeric?.byValue[raw];
2578
+ if (parsed && col.binning.edges) {
2579
+ const bin = pickBin(parsed.value, col.binning);
2580
+ if (bin !== null) value = bin;
2581
+ }
2582
+ }
2583
+ return value;
2584
+ }
2585
+ function pickBin(value, b) {
2586
+ if (!b.edges || b.edges.length === 0) return null;
2587
+ for (let i = 0; i < b.edges.length; i++) if (value < b.edges[i]) return b.labels?.[i] ?? `< ${b.edges[i]}`;
2588
+ const last = b.edges[b.edges.length - 1];
2589
+ return b.labels?.[b.edges.length] ?? `> ${last}`;
2590
+ }
2591
+ function composeGroups(input) {
2592
+ const experimentalGroups = [];
2593
+ const qcGroups = [];
2594
+ const excludedSamples = [];
2595
+ const metadata = [];
2596
+ let colorIdx = 0;
2597
+ for (const cls of input.classes) {
2598
+ const schema = input.schemas[classKey(cls)];
2599
+ if (!schema) continue;
2600
+ if (cls.disposition === "exclude") {
2601
+ for (const m of cls.members) excludedSamples.push(input.sampleNames[m]);
2602
+ continue;
2603
+ }
2604
+ const groupMap = /* @__PURE__ */ new Map();
2605
+ const colByIdx = new Map(schema.columns.map((c) => [c.index, c]));
2606
+ for (const m of cls.members) {
2607
+ const tokens = input.tokenizedSamples[m];
2608
+ const rowName = input.sampleNames[m];
2609
+ let excluded = false;
2610
+ const fieldValues = {};
2611
+ for (const col of schema.columns) {
2612
+ const display = applyOps(col, col.sourceIndices.map((idx) => tokens[idx] ?? "").join("_"));
2613
+ if (display === null) {
2614
+ excluded = true;
2615
+ break;
2616
+ }
2617
+ fieldValues[col.displayName ?? col.name] = display;
2618
+ }
2619
+ if (excluded) {
2620
+ excludedSamples.push(rowName);
2621
+ continue;
2622
+ }
2623
+ const keyParts = cls.disposition === "group" ? schema.groupBy.map((idx) => {
2624
+ const col = colByIdx.get(idx);
2625
+ return fieldValues[col.displayName ?? col.name];
2626
+ }) : [cls.label];
2627
+ const groupKey = cls.disposition === "group" && cls.kind !== "unknown" && keyParts.length > 0 ? `${cls.label} / ${keyParts.join(" / ")}` : keyParts.length > 0 ? keyParts.join(" / ") : cls.label;
2628
+ const bucket = groupMap.get(groupKey);
2629
+ if (bucket) bucket.samples.push(rowName);
2630
+ else groupMap.set(groupKey, {
2631
+ name: groupKey,
2632
+ samples: [rowName]
2633
+ });
2634
+ metadata.push({
2635
+ sampleName: rowName,
2636
+ fields: fieldValues,
2637
+ group: groupKey
2638
+ });
2639
+ }
2640
+ for (const { name, samples } of groupMap.values()) {
2641
+ const g = {
2642
+ name,
2643
+ color: cls.disposition === "overlay" ? QC_OVERLAY_COLOR : DEFAULT_COLORS[colorIdx++ % DEFAULT_COLORS.length],
2644
+ samples
2645
+ };
2646
+ if (cls.disposition === "overlay") qcGroups.push(g);
2647
+ else experimentalGroups.push(g);
2648
+ }
2649
+ }
2650
+ return {
2651
+ groups: [...experimentalGroups, ...qcGroups],
2652
+ experimentalGroups,
2653
+ qcGroups,
2654
+ metadata,
2655
+ excludedSamples,
2656
+ schemas: Object.values(input.schemas)
2657
+ };
2658
+ }
2659
+ //#endregion
2660
+ //#region src/composables/autoGroup/fingerprint.ts
2661
+ function serializeFingerprint(schemas) {
2662
+ return {
2663
+ version: 1,
2664
+ classes: schemas.map((s) => ({
2665
+ kind: s.classKind,
2666
+ subKind: s.subKind,
2667
+ columns: s.columns.map((c) => ({
2668
+ name: c.displayName ?? c.name,
2669
+ role: c.role ?? "factor",
2670
+ sourceIndices: c.sourceIndices,
2671
+ binning: c.binning,
2672
+ ops: c.ops
2673
+ })),
2674
+ groupBy: s.groupBy
2675
+ }))
2676
+ };
2677
+ }
2678
+ function restoreFingerprint(fp, current) {
2679
+ return fp.classes.map((snap) => {
2680
+ const target = current.find((c) => c.classKind === snap.kind && c.subKind === snap.subKind);
2681
+ if (!target) throw new Error(`Fingerprint class not present in current input: ${snap.kind}${snap.subKind ? "/" + snap.subKind : ""}`);
2682
+ if (target.columns.length !== snap.columns.length) throw new Error(`Saved schema expects ${snap.columns.length} columns, current data has ${target.columns.length} for class ${snap.kind}${snap.subKind ? "/" + snap.subKind : ""}. column count mismatch`);
2683
+ return {
2684
+ ...target,
2685
+ groupBy: snap.groupBy,
2686
+ columns: target.columns.map((col, i) => ({
2687
+ ...col,
2688
+ displayName: snap.columns[i].name,
2689
+ role: snap.columns[i].role,
2690
+ binning: snap.columns[i].binning,
2691
+ ops: snap.columns[i].ops
2692
+ }))
2693
+ };
2694
+ });
2695
+ }
2696
+ //#endregion
2697
+ //#region src/composables/autoGroup/template.ts
2698
+ var DEFAULT_BLANK_HEADERS = [
2699
+ "group",
2700
+ "class",
2701
+ "replicate",
2702
+ "notes"
2703
+ ];
2704
+ function rfc4180Quote(cell) {
2705
+ if (/[",\n]/.test(cell)) return `"${cell.replace(/"/g, "\"\"")}"`;
2706
+ return cell;
2707
+ }
2708
+ function joinRow(cells, sep) {
2709
+ return sep === "," ? cells.map(rfc4180Quote).join(",") : cells.join(" ");
2710
+ }
2711
+ function today() {
2712
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2713
+ }
2714
+ function composeTemplate(samples, schemas, classes, options) {
2715
+ const sep = options.format === "tsv" ? " " : ",";
2716
+ const ext = options.format === "tsv" ? "tsv" : "csv";
2717
+ const filename = `${options.experimentCode ? `${options.experimentCode}_metadata` : "sample_metadata"}_${today()}.${ext}`;
2718
+ if (options.mode === "blank" || !schemas || !classes) {
2719
+ const headers = ["sample_name", ...options.suggestedColumns ?? DEFAULT_BLANK_HEADERS];
2720
+ const rows = samples.map((s) => {
2721
+ return joinRow([s, ...Array(headers.length - 1).fill("")], sep);
2722
+ });
2723
+ return {
2724
+ content: [joinRow(headers, sep), ...rows].join("\n"),
2725
+ filename
2726
+ };
2727
+ }
2728
+ const tokenized = options.tokenizedSamples ?? [];
2729
+ const allColumns = [];
2730
+ const sampleToClass = /* @__PURE__ */ new Map();
2731
+ for (const cls of classes) for (const m of cls.members) sampleToClass.set(m, cls);
2732
+ const schemaByKey = /* @__PURE__ */ new Map();
2733
+ for (const s of schemas) {
2734
+ const k = s.subKind ? `${s.classKind}:${s.subKind}` : s.classKind;
2735
+ schemaByKey.set(k, s);
2736
+ }
2737
+ const colByNameByKey = /* @__PURE__ */ new Map();
2738
+ const groupBySetByKey = /* @__PURE__ */ new Map();
2739
+ for (const s of schemas) {
2740
+ const k = s.subKind ? `${s.classKind}:${s.subKind}` : s.classKind;
2741
+ const m = /* @__PURE__ */ new Map();
2742
+ for (const c of s.columns) m.set(c.displayName ?? c.name, c);
2743
+ colByNameByKey.set(k, m);
2744
+ groupBySetByKey.set(k, new Set(s.groupBy));
2745
+ const seenColNames = /* @__PURE__ */ new Set();
2746
+ for (const c of s.columns) {
2747
+ const name = c.displayName ?? c.name;
2748
+ if (!seenColNames.has(name)) {
2749
+ seenColNames.add(name);
2750
+ if (!allColumns.some((x) => x.colName === name)) allColumns.push({
2751
+ schemaKey: k,
2752
+ colName: name
2753
+ });
2754
+ }
2755
+ }
2756
+ }
2757
+ const headers = [
2758
+ "sample_name",
2759
+ "class",
2760
+ ...allColumns.map((c) => c.colName),
2761
+ "group",
2762
+ "notes"
2763
+ ];
2764
+ const rows = [];
2765
+ for (let i = 0; i < samples.length; i++) {
2766
+ const cls = sampleToClass.get(i);
2767
+ const classLabel = cls?.label ?? "";
2768
+ const tokens = tokenized[i] ?? [];
2769
+ const cells = [samples[i], classLabel];
2770
+ if (cls) {
2771
+ const k = classKey(cls);
2772
+ const groupBySet = groupBySetByKey.get(k);
2773
+ const colByName = colByNameByKey.get(k);
2774
+ const keyParts = [];
2775
+ for (const col of allColumns) if (col.schemaKey === k && colByName) {
2776
+ const schemaCol = colByName.get(col.colName);
2777
+ const raw = schemaCol?.sourceIndices.map((idx) => tokens[idx] ?? "").join("_") ?? "";
2778
+ cells.push(raw);
2779
+ if (schemaCol && groupBySet?.has(schemaCol.index)) keyParts.push(raw);
2780
+ } else cells.push("");
2781
+ cells.push(keyParts.join(" / "));
2782
+ } else {
2783
+ for (const _ of allColumns) cells.push("");
2784
+ cells.push("");
2785
+ }
2786
+ cells.push("");
2787
+ rows.push(joinRow(cells, sep));
2788
+ }
2789
+ return {
2790
+ content: [joinRow(headers, sep), ...rows].join("\n"),
2791
+ filename
2792
+ };
2793
+ }
2794
+ //#endregion
2795
+ //#region src/composables/autoGroup/replicatePreGroup.ts
2796
+ /**
2797
+ * Replicate pre-grouping.
2798
+ *
2799
+ * Strip the replicate / injection-number markers from each sample's name, then
2800
+ * collapse samples that share a base name. Those samples ARE replicates of one
2801
+ * another by definition; the remaining unique base names are what the rest of
2802
+ * the pipeline (tokenize → classify → schema → compose) should operate on.
2803
+ *
2804
+ * This pre-pass replaces the older "treat every sample as independent, then
2805
+ * infer replicate columns" approach. Identifying replicate-equivalence by name
2806
+ * before tokenisation removes a class of fragile cardinality heuristics.
2807
+ */
2808
+ var STRIP_PATTERNS = vocab_default.replicateStripPatterns.map((src) => new RegExp(src, "gi"));
2809
+ /**
2810
+ * Iteratively strip every replicate / injection-number marker from `name`.
2811
+ * Each pattern is applied repeatedly until it stops matching, so a sample like
2812
+ * `study_T1_Rep2_004` collapses to `study` in one call.
2813
+ */
2814
+ function stripReplicateTokens(name) {
2815
+ let base = name;
2816
+ for (const re of STRIP_PATTERNS) {
2817
+ let prev;
2818
+ do {
2819
+ prev = base;
2820
+ base = base.replace(re, "");
2821
+ re.lastIndex = 0;
2822
+ } while (base !== prev);
2823
+ }
2824
+ return base;
2825
+ }
2826
+ function preGroupReplicates(samples) {
2827
+ const map = /* @__PURE__ */ new Map();
2828
+ const baseOrder = [];
2829
+ for (let i = 0; i < samples.length; i++) {
2830
+ const base = stripReplicateTokens(samples[i]);
2831
+ const bucket = map.get(base);
2832
+ if (bucket) bucket.push(i);
2833
+ else {
2834
+ map.set(base, [i]);
2835
+ baseOrder.push(base);
2836
+ }
2837
+ }
2838
+ return {
2839
+ baseNames: baseOrder,
2840
+ membersByBase: baseOrder.map((b) => map.get(b)),
2841
+ originalSamples: samples
2842
+ };
2843
+ }
2844
+ /**
2845
+ * Expand groups produced over base names back to groups of original sample
2846
+ * names. Each entry in `group.samples` is treated as a base name and replaced
2847
+ * by the list of original samples that collapsed onto it.
2848
+ */
2849
+ function expandGroupsWithReplicates(groups, preGrouping) {
2850
+ const baseToMembers = /* @__PURE__ */ new Map();
2851
+ for (let i = 0; i < preGrouping.baseNames.length; i++) baseToMembers.set(preGrouping.baseNames[i], preGrouping.membersByBase[i]);
2852
+ return groups.map((g) => ({
2853
+ ...g,
2854
+ samples: g.samples.flatMap((baseName) => {
2855
+ const indices = baseToMembers.get(baseName);
2856
+ return indices ? indices.map((i) => preGrouping.originalSamples[i]) : [baseName];
2857
+ })
2858
+ }));
2859
+ }
2860
+ //#endregion
2101
2861
  //#region src/composables/experimentDesignData.ts
2102
2862
  function isRecord(value) {
2103
2863
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -2200,119 +2960,17 @@ function extractSampleNamesFromDesignData(rawData, options = {}) {
2200
2960
  return extractSampleOptionsFromDesignData(rawData, options).map((sample) => sample.value);
2201
2961
  }
2202
2962
  //#endregion
2203
- //#region src/composables/useAutoGroup.ts
2204
- var DEFAULT_COLORS = [
2205
- "#3B82F6",
2206
- "#10B981",
2207
- "#F59E0B",
2208
- "#EF4444",
2209
- "#8B5CF6",
2210
- "#EC4899",
2211
- "#06B6D4",
2212
- "#84CC16",
2213
- "#F97316",
2214
- "#6366F1"
2215
- ];
2216
- var DELIMITER_CANDIDATES = [
2217
- "_",
2218
- "-",
2219
- "."
2220
- ];
2221
- function analyzeDelimiter(lines) {
2222
- if (lines.length === 0) return {
2223
- delimiter: "_",
2224
- dominantFieldCount: 1,
2225
- minFieldCount: 1,
2226
- consistency: 0
2227
- };
2228
- let bestDelimiter = "_";
2229
- let bestConsistency = -1;
2230
- let bestFieldCount = 1;
2231
- for (const candidate of DELIMITER_CANDIDATES) {
2232
- const fieldCounts = lines.map((line) => line.split(candidate).length);
2233
- const countFrequency = /* @__PURE__ */ new Map();
2234
- for (const count of fieldCounts) countFrequency.set(count, (countFrequency.get(count) ?? 0) + 1);
2235
- let modeCount = 1;
2236
- let modeFrequency = 0;
2237
- for (const [count, freq] of countFrequency) if (freq > modeFrequency || freq === modeFrequency && count > modeCount) {
2238
- modeCount = count;
2239
- modeFrequency = freq;
2240
- }
2241
- const rawConsistency = modeFrequency / lines.length;
2242
- const consistency = modeCount > 1 ? rawConsistency : 0;
2243
- if (consistency > bestConsistency || consistency === bestConsistency && DELIMITER_CANDIDATES.indexOf(candidate) < DELIMITER_CANDIDATES.indexOf(bestDelimiter)) {
2244
- bestDelimiter = candidate;
2245
- bestConsistency = consistency;
2246
- bestFieldCount = modeCount;
2247
- }
2248
- }
2249
- const multiFieldCounts = lines.map((line) => line.split(bestDelimiter).length).filter((c) => c >= 2);
2250
- const minFieldCount = multiFieldCounts.length > 0 ? Math.min(...multiFieldCounts) : 1;
2251
- return {
2252
- delimiter: bestDelimiter,
2253
- dominantFieldCount: bestFieldCount,
2254
- minFieldCount,
2255
- consistency: bestConsistency
2256
- };
2257
- }
2258
- function detectOutliers(lines, delimiter, minFieldCount) {
2259
- const outliers = [];
2260
- for (let i = 0; i < lines.length; i++) {
2261
- const fieldCount = lines[i].split(delimiter).length;
2262
- if (fieldCount < minFieldCount) outliers.push({
2263
- sample: lines[i],
2264
- index: i,
2265
- fieldCount,
2266
- action: "include"
2267
- });
2268
- }
2269
- return outliers;
2270
- }
2271
- var QC_KEYWORDS = new Set([
2272
- "eqc",
2273
- "iqc",
2274
- "qc",
2275
- "blank",
2276
- "std",
2277
- "standard",
2278
- "test"
2279
- ]);
2280
- function classifyOutlierAction(sample, delimiter) {
2281
- return sample.split(delimiter).some((seg) => QC_KEYWORDS.has(seg.toLowerCase())) ? "qc" : "include";
2282
- }
2283
- function isUsefulField(field, rowCount) {
2284
- return field.cardinality > 1 && !(rowCount > 1 && field.cardinality === rowCount);
2285
- }
2286
- function extractColumns(samples, delimiter, minFieldCount) {
2287
- if (samples.length === 0) return [];
2288
- const suffixCount = minFieldCount - 1;
2289
- const rows = samples.map((s) => {
2290
- const parts = s.split(delimiter);
2291
- const splitAt = parts.length - suffixCount;
2292
- return [parts.slice(0, splitAt).join(delimiter), ...parts.slice(splitAt)];
2293
- });
2294
- const columnCount = minFieldCount;
2295
- const columns = [];
2296
- for (let col = 0; col < columnCount; col++) {
2297
- const values = rows.map((row) => row[col]);
2298
- const unique = [...new Set(values)];
2299
- columns.push({
2300
- index: col,
2301
- name: col === 0 ? "Condition" : `Field ${col + 1}`,
2302
- uniqueValues: unique,
2303
- cardinality: unique.length,
2304
- type: col === 0 ? "prefix" : "suffix"
2305
- });
2306
- }
2307
- return columns;
2308
- }
2963
+ //#region src/composables/autoGroup/csv-shim.ts
2309
2964
  function parseCSVLine(line, delimiter = ",") {
2310
2965
  const result = [];
2311
2966
  let current = "";
2312
2967
  let inQuotes = false;
2313
2968
  for (let i = 0; i < line.length; i++) {
2314
2969
  const char = line[i];
2315
- if (char === "\"") inQuotes = !inQuotes;
2970
+ if (char === "\"") if (inQuotes && line[i + 1] === "\"") {
2971
+ current += "\"";
2972
+ i++;
2973
+ } else inQuotes = !inQuotes;
2316
2974
  else if (char === delimiter && !inQuotes) {
2317
2975
  result.push(current.trim());
2318
2976
  current = "";
@@ -2332,8 +2990,8 @@ function parseCSV(text) {
2332
2990
  const values = parseCSVLine(lines[i], csvDelimiter);
2333
2991
  if (values.length !== headers.length) continue;
2334
2992
  const row = {};
2335
- headers.forEach((header, idx) => {
2336
- row[header] = values[idx];
2993
+ headers.forEach((h, idx) => {
2994
+ row[h] = values[idx];
2337
2995
  });
2338
2996
  rows.push(row);
2339
2997
  }
@@ -2354,301 +3012,407 @@ function parseCSV(text) {
2354
3012
  delimiter: csvDelimiter
2355
3013
  };
2356
3014
  }
2357
- function computeGroups(allSamples, columns, enabledFields, outlierActions, delimiter, minFieldCount) {
2358
- const excludedSamples = [];
2359
- const qcSamples = [];
2360
- const conformingSamples = [];
2361
- for (let i = 0; i < allSamples.length; i++) {
2362
- const action = outlierActions.get(i);
2363
- if (action === "exclude") excludedSamples.push(allSamples[i]);
2364
- else if (action === "qc") qcSamples.push(allSamples[i]);
2365
- else conformingSamples.push(allSamples[i]);
2366
- }
2367
- const groupMap = /* @__PURE__ */ new Map();
2368
- const metadata = [];
2369
- const enabledIndices = [...enabledFields].sort((a, b) => a - b);
2370
- const suffixCount = minFieldCount - 1;
2371
- for (const sample of conformingSamples) {
2372
- const parts = sample.split(delimiter);
2373
- const splitAt = Math.max(1, parts.length - suffixCount);
2374
- const row = [parts.slice(0, splitAt).join(delimiter), ...parts.slice(splitAt)];
2375
- const keyParts = [];
2376
- for (const idx of enabledIndices) if (idx < row.length && idx < columns.length) keyParts.push(row[idx]);
2377
- const groupKey = keyParts.join(" / ");
2378
- const group = groupMap.get(groupKey);
2379
- if (group) group.push(sample);
2380
- else groupMap.set(groupKey, [sample]);
2381
- const fields = {};
2382
- for (const col of columns) if (col.index < row.length) fields[col.name] = row[col.index];
2383
- metadata.push({
2384
- sampleName: sample,
2385
- fields,
2386
- group: groupKey
2387
- });
3015
+ //#endregion
3016
+ //#region src/composables/useAutoGroup.ts
3017
+ var deprecationWarned = false;
3018
+ var outlierDeprecationWarned = false;
3019
+ function warnDeprecated(api) {
3020
+ if (deprecationWarned) return;
3021
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
3022
+ console.warn(`[useAutoGroup] ${api} is deprecated; use the new class-first API (setClassDisposition / toggleGroupBy / setValueOps).`);
3023
+ deprecationWarned = true;
3024
+ }
3025
+ function useAutoGroup() {
3026
+ const inputMode = ref("paste");
3027
+ const rawText = ref("");
3028
+ const csvData = ref(null);
3029
+ const sampleTypeHints = ref([]);
3030
+ const classes = ref([]);
3031
+ const schemas = ref({});
3032
+ const activeClassKey = ref("");
3033
+ const samples = computed(() => {
3034
+ if (csvData.value) return csvData.value.rows.map((r) => r[csvData.value.sampleColumn] ?? "");
3035
+ return rawText.value.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
3036
+ });
3037
+ const tokenized = ref([]);
3038
+ const delimiter = ref("_");
3039
+ const preGrouping = ref(null);
3040
+ function parseInput() {
3041
+ if ((inputMode.value === "csv" || inputMode.value === "experiment") && csvData.value) {
3042
+ parseInputFromCsv();
3043
+ return;
3044
+ }
3045
+ parseInputFromPaste();
2388
3046
  }
2389
- const groups = [];
2390
- let colorIdx = 0;
2391
- for (const [name, samples] of groupMap) {
2392
- groups.push({
2393
- name,
2394
- color: DEFAULT_COLORS[colorIdx % DEFAULT_COLORS.length],
2395
- samples
2396
- });
2397
- colorIdx++;
3047
+ function parseInputFromCsv() {
3048
+ preGrouping.value = null;
3049
+ const csv = csvData.value;
3050
+ const nonSampleCols = csv.columns.filter((c) => c !== csv.sampleColumn && c !== "sample_type");
3051
+ if (nonSampleCols.length === 0) {
3052
+ tokenized.value = csv.rows.map(() => []);
3053
+ classes.value = [{
3054
+ kind: "unknown",
3055
+ label: "Unknown",
3056
+ members: csv.rows.map((_, i) => i),
3057
+ classTagPositions: [],
3058
+ disposition: "group"
3059
+ }];
3060
+ schemas.value = { unknown: {
3061
+ classKind: "unknown",
3062
+ columns: [],
3063
+ groupBy: []
3064
+ } };
3065
+ activeClassKey.value = "unknown";
3066
+ return;
3067
+ }
3068
+ const tokens = csv.rows.map((r) => nonSampleCols.map((col) => r[col] ?? ""));
3069
+ tokenized.value = tokens;
3070
+ const detected = detectClass(tokens, { sampleTypeHints: csv.rows.map((r) => {
3071
+ const t = String(r["sample_type"] ?? "").toLowerCase();
3072
+ if (t === "qc") return "qc";
3073
+ if (t === "blank") return "blank";
3074
+ }) });
3075
+ classes.value = detected;
3076
+ const newSchemas = {};
3077
+ for (const cls of detected) {
3078
+ const schema = buildClassSchema(cls.members.map((i) => tokens[i]), cls.kind, cls.subKind, cls.classTagPositions);
3079
+ schema.columns = schema.columns.map((c, i) => ({
3080
+ ...c,
3081
+ name: nonSampleCols[i] ?? c.name,
3082
+ originalName: nonSampleCols[i]
3083
+ }));
3084
+ newSchemas[classKey(cls)] = schema;
3085
+ }
3086
+ schemas.value = newSchemas;
3087
+ if (detected.length > 0) activeClassKey.value = classKey(detected[0]);
2398
3088
  }
2399
- if (qcSamples.length > 0) {
2400
- groups.push({
2401
- name: "QC",
2402
- color: "#6B7280",
2403
- samples: qcSamples
3089
+ function parseInputFromPaste() {
3090
+ const lines = samples.value;
3091
+ if (lines.length === 0) {
3092
+ classes.value = [];
3093
+ schemas.value = {};
3094
+ preGrouping.value = null;
3095
+ return;
3096
+ }
3097
+ const pre = preGroupReplicates(lines);
3098
+ preGrouping.value = pre;
3099
+ const d = pickPrimaryDelimiter(pre.baseNames);
3100
+ delimiter.value = d;
3101
+ tokenized.value = pre.baseNames.map((l) => splitMulti(l, d));
3102
+ const hints = pre.membersByBase.map((members) => sampleTypeHints.value[members[0]]);
3103
+ const detected = detectClass(tokenized.value, { sampleTypeHints: hints });
3104
+ classes.value = detected;
3105
+ const newSchemas = {};
3106
+ for (const cls of detected) {
3107
+ const memberTokens = cls.members.map((i) => tokenized.value[i]);
3108
+ newSchemas[classKey(cls)] = buildClassSchema(memberTokens, cls.kind, cls.subKind, cls.classTagPositions);
3109
+ }
3110
+ schemas.value = newSchemas;
3111
+ if (detected.length > 0) activeClassKey.value = classKey(detected[0]);
3112
+ }
3113
+ const activeSchema = computed(() => schemas.value[activeClassKey.value] ?? null);
3114
+ const suggestions = computed(() => {
3115
+ const schema = activeSchema.value;
3116
+ if (!schema) return [];
3117
+ const activeClass = classes.value.find((c) => classKey(c) === activeClassKey.value);
3118
+ if (!activeClass) return [];
3119
+ return findMerges(schema, activeClass.members.map((i) => tokenized.value[i]));
3120
+ });
3121
+ const result = computed(() => {
3122
+ const pre = preGrouping.value;
3123
+ const composed = composeGroups({
3124
+ tokenizedSamples: tokenized.value,
3125
+ sampleNames: pre ? pre.baseNames : samples.value,
3126
+ schemas: schemas.value,
3127
+ classes: classes.value
2404
3128
  });
2405
- for (const sample of qcSamples) metadata.push({
2406
- sampleName: sample,
2407
- fields: {},
2408
- group: "QC"
3129
+ if (!pre) return composed;
3130
+ return {
3131
+ ...composed,
3132
+ groups: expandGroupsWithReplicates(composed.groups, pre),
3133
+ experimentalGroups: composed.experimentalGroups ? expandGroupsWithReplicates(composed.experimentalGroups, pre) : void 0,
3134
+ qcGroups: composed.qcGroups ? expandGroupsWithReplicates(composed.qcGroups, pre) : void 0
3135
+ };
3136
+ });
3137
+ const fingerprint = computed(() => serializeFingerprint(result.value.schemas ?? []));
3138
+ const groups = computed(() => result.value.groups);
3139
+ const qcGroups = computed(() => result.value.qcGroups ?? []);
3140
+ const metadata = computed(() => result.value.metadata);
3141
+ const excludedSamples = computed(() => result.value.excludedSamples);
3142
+ function setClassDisposition(key, disposition) {
3143
+ const target = classes.value.find((c) => classKey(c) === key);
3144
+ if (!target || target.disposition === disposition) return;
3145
+ classes.value = classes.value.map((c) => classKey(c) === key ? {
3146
+ ...c,
3147
+ disposition
3148
+ } : c);
3149
+ }
3150
+ function setClassDispositions(predicate, disposition) {
3151
+ let changed = false;
3152
+ const next = classes.value.map((c) => {
3153
+ if (predicate(c) && c.disposition !== disposition) {
3154
+ changed = true;
3155
+ return {
3156
+ ...c,
3157
+ disposition
3158
+ };
3159
+ }
3160
+ return c;
2409
3161
  });
2410
- }
2411
- return {
2412
- groups,
2413
- metadata,
2414
- excludedSamples
2415
- };
2416
- }
2417
- /**
2418
- * Extract sample metadata from raw design_data into ParsedCsvData format.
2419
- *
2420
- * Looks for a `samples` array in the design data. For each sample, merges
2421
- * the `conditions` dict (the metadata table) with the `sample_name` to
2422
- * produce a flat tabular row. QC and blank samples are filtered out by
2423
- * their explicit `sample_type` field.
2424
- *
2425
- * Returns null if no samples with conditions are found.
2426
- */
2427
- function extractSamplesFromDesignData(rawData) {
2428
- const designData = unwrapExperimentDesignData(rawData);
2429
- if (!designData) return null;
2430
- const samples = designData.samples;
2431
- if (!Array.isArray(samples) || samples.length === 0) return null;
2432
- const allConditionKeys = [];
2433
- const keySet = /* @__PURE__ */ new Set();
2434
- const filteredSamples = [];
2435
- for (const sample of samples) {
2436
- const sampleType = String(sample.sample_type ?? "sample").toLowerCase();
2437
- if (sampleType === "qc" || sampleType === "blank") continue;
2438
- filteredSamples.push(sample);
2439
- const conditions = sample.conditions;
2440
- if (conditions && typeof conditions === "object") {
2441
- for (const key of Object.keys(conditions)) if (!keySet.has(key)) {
2442
- keySet.add(key);
2443
- allConditionKeys.push(key);
3162
+ if (changed) classes.value = next;
3163
+ }
3164
+ function toggleGroupBy(key, idx) {
3165
+ const schema = schemas.value[key];
3166
+ if (!schema) return;
3167
+ const set = new Set(schema.groupBy);
3168
+ if (set.has(idx)) set.delete(idx);
3169
+ else set.add(idx);
3170
+ schemas.value = {
3171
+ ...schemas.value,
3172
+ [key]: {
3173
+ ...schema,
3174
+ groupBy: [...set].sort((a, b) => a - b)
2444
3175
  }
3176
+ };
3177
+ }
3178
+ function updateColumn(key, idx, patch) {
3179
+ const schema = schemas.value[key];
3180
+ if (!schema) return;
3181
+ const col = schema.columns.find((c) => c.index === idx);
3182
+ if (!col) return;
3183
+ let changed = false;
3184
+ for (const k of Object.keys(patch)) if (col[k] !== patch[k]) {
3185
+ changed = true;
3186
+ break;
2445
3187
  }
3188
+ if (!changed) return;
3189
+ schemas.value = {
3190
+ ...schemas.value,
3191
+ [key]: {
3192
+ ...schema,
3193
+ columns: schema.columns.map((c) => c.index === idx ? {
3194
+ ...c,
3195
+ ...patch
3196
+ } : c)
3197
+ }
3198
+ };
2446
3199
  }
2447
- if (filteredSamples.length === 0 || allConditionKeys.length === 0) return null;
2448
- return {
2449
- columns: ["sample_name", ...allConditionKeys],
2450
- rows: filteredSamples.map((sample) => {
2451
- const conditions = sample.conditions ?? {};
2452
- const row = { sample_name: String(sample.sample_name ?? "") };
2453
- for (const key of allConditionKeys) row[key] = conditions[key] ?? "";
2454
- return row;
2455
- }),
2456
- sampleColumn: "sample_name",
2457
- delimiter: ","
2458
- };
2459
- }
2460
- function computeGroupsFromCsv(csvData, columns, enabledFields) {
2461
- const groupMap = /* @__PURE__ */ new Map();
2462
- const metadata = [];
2463
- const enabledCols = columns.filter((c) => enabledFields.has(c.index)).sort((a, b) => a.index - b.index);
2464
- for (const row of csvData.rows) {
2465
- const sampleName = row[csvData.sampleColumn];
2466
- const groupKey = enabledCols.map((col) => row[col.originalName ?? col.name]).join(" / ");
2467
- const group = groupMap.get(groupKey);
2468
- if (group) group.push(sampleName);
2469
- else groupMap.set(groupKey, [sampleName]);
2470
- const fields = {};
2471
- for (const col of columns) fields[col.name] = row[col.originalName ?? col.name];
2472
- metadata.push({
2473
- sampleName,
2474
- fields,
2475
- group: groupKey
2476
- });
3200
+ function setRole(key, idx, role) {
3201
+ updateColumn(key, idx, { role });
2477
3202
  }
2478
- const groups = [];
2479
- let colorIdx = 0;
2480
- for (const [name, samples] of groupMap) {
2481
- groups.push({
2482
- name,
2483
- color: DEFAULT_COLORS[colorIdx % DEFAULT_COLORS.length],
2484
- samples
3203
+ function setColumnName(key, idx, name) {
3204
+ updateColumn(key, idx, { displayName: name });
3205
+ }
3206
+ function setValueOps(key, idx, ops) {
3207
+ updateColumn(key, idx, { ops });
3208
+ }
3209
+ function setBinning(key, idx, binning) {
3210
+ updateColumn(key, idx, { binning });
3211
+ }
3212
+ function mergeColumns(key, indices) {
3213
+ const schema = schemas.value[key];
3214
+ if (!schema || indices.length < 2) return;
3215
+ const sorted = [...indices].sort((a, b) => a - b);
3216
+ const start = sorted[0];
3217
+ const merged = {
3218
+ index: start,
3219
+ name: `Column ${sorted.map((i) => i + 1).join("–")}`,
3220
+ sourceIndices: sorted.flatMap((i) => schema.columns.find((c) => c.index === i)?.sourceIndices ?? [i]),
3221
+ uniqueValues: [],
3222
+ cardinality: 0,
3223
+ role: "factor"
3224
+ };
3225
+ const dropped = new Set(sorted);
3226
+ schemas.value = {
3227
+ ...schemas.value,
3228
+ [key]: {
3229
+ ...schema,
3230
+ columns: [merged, ...schema.columns.filter((c) => !dropped.has(c.index))].sort((a, b) => a.index - b.index),
3231
+ groupBy: schema.groupBy.filter((i) => !dropped.has(i) || i === start)
3232
+ }
3233
+ };
3234
+ }
3235
+ function loadFingerprint(fp) {
3236
+ const restored = restoreFingerprint(fp, Object.values(schemas.value));
3237
+ const next = {};
3238
+ for (const s of restored) next[classKey({
3239
+ kind: s.classKind,
3240
+ subKind: s.subKind
3241
+ })] = s;
3242
+ schemas.value = next;
3243
+ }
3244
+ const canDownloadTemplate = computed(() => samples.value.length > 0);
3245
+ function downloadTemplate(mode = "prefilled", format = "csv") {
3246
+ const { content, filename } = composeTemplate(samples.value, mode === "prefilled" ? Object.values(schemas.value) : null, mode === "prefilled" ? classes.value : null, {
3247
+ mode,
3248
+ format,
3249
+ tokenizedSamples: tokenized.value
3250
+ });
3251
+ const blob = new Blob([content], { type: format === "tsv" ? "text/tab-separated-values" : "text/csv" });
3252
+ const url = URL.createObjectURL(blob);
3253
+ const a = document.createElement("a");
3254
+ a.href = url;
3255
+ a.download = filename;
3256
+ document.body.appendChild(a);
3257
+ a.click();
3258
+ document.body.removeChild(a);
3259
+ URL.revokeObjectURL(url);
3260
+ }
3261
+ /** Test seam — exposes the template content/filename without triggering a browser download. */
3262
+ function composeTemplateForTest(opts) {
3263
+ return composeTemplate(samples.value, opts.mode === "prefilled" ? Object.values(schemas.value) : null, opts.mode === "prefilled" ? classes.value : null, {
3264
+ ...opts,
3265
+ tokenizedSamples: tokenized.value
2485
3266
  });
2486
- colorIdx++;
2487
3267
  }
2488
- return {
2489
- groups,
2490
- metadata,
2491
- excludedSamples: []
2492
- };
2493
- }
2494
- /** Parses sample names or CSV data to propose group assignments with outlier detection and preview. */
2495
- function useAutoGroup() {
2496
- const inputMode = ref("paste");
2497
- const rawText = ref("");
2498
- const csvData = ref(null);
2499
- const delimiter = ref("_");
2500
- const dominantFieldCount = ref(1);
2501
- const minFieldCount = ref(1);
2502
3268
  const outliers = ref([]);
2503
- const fields = ref([]);
2504
- const fieldNames = ref({});
2505
- const enabledFields = ref(/* @__PURE__ */ new Set());
2506
- const isTabularMode = computed(() => (inputMode.value === "csv" || inputMode.value === "experiment") && csvData.value !== null);
2507
- const samples = computed(() => {
2508
- const data = csvData.value;
2509
- if (isTabularMode.value && data) return data.rows.map((r) => r[data.sampleColumn]);
2510
- return rawText.value.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
2511
- });
2512
3269
  const hasOutliers = computed(() => outliers.value.length > 0);
2513
- const conformingSamples = computed(() => {
2514
- const outlierIndices = new Set(outliers.value.map((o) => o.index));
2515
- return samples.value.filter((_, i) => !outlierIndices.has(i));
2516
- });
2517
- const outlierActions = computed(() => {
2518
- const map = /* @__PURE__ */ new Map();
2519
- for (const o of outliers.value) map.set(o.index, o.action);
2520
- return map;
3270
+ const conformingSamples = computed(() => samples.value);
3271
+ const dominantFieldCount = computed(() => tokenized.value[0]?.length ?? 0);
3272
+ const minFieldCount = computed(() => {
3273
+ if (tokenized.value.length === 0) return 0;
3274
+ return Math.min(...tokenized.value.map((t) => t.length));
2521
3275
  });
2522
- const effectiveColumns = computed(() => {
2523
- return fields.value.map((col) => ({
2524
- ...col,
2525
- name: fieldNames.value[col.index] ?? col.name
2526
- }));
2527
- });
2528
- const _computedResult = computed(() => {
2529
- if (effectiveColumns.value.length === 0 || enabledFields.value.size === 0) return {
2530
- groups: [],
2531
- metadata: [],
2532
- excludedSamples: []
2533
- };
2534
- if (isTabularMode.value && csvData.value) return computeGroupsFromCsv(csvData.value, effectiveColumns.value, enabledFields.value);
2535
- return computeGroups(samples.value, effectiveColumns.value, enabledFields.value, outlierActions.value, delimiter.value, minFieldCount.value);
3276
+ const fields = computed(() => activeSchema.value?.columns ?? []);
3277
+ const fieldNames = computed(() => {
3278
+ const out = {};
3279
+ for (const c of fields.value) out[c.index] = c.displayName ?? c.name;
3280
+ return out;
2536
3281
  });
2537
- const groups = computed(() => _computedResult.value.groups);
2538
- const metadata = computed(() => _computedResult.value.metadata);
2539
- const excludedSamples = computed(() => _computedResult.value.excludedSamples);
3282
+ const enabledFields = computed(() => activeSchema.value ? new Set(activeSchema.value.groupBy) : /* @__PURE__ */ new Set());
3283
+ const effectiveColumns = fields;
2540
3284
  const allSingletons = computed(() => groups.value.length > 1 && groups.value.every((g) => g.samples.length === 1));
2541
- const result = _computedResult;
2542
- function parseInput() {
2543
- if (isTabularMode.value) parseCsvInput();
2544
- else parsePasteInput();
2545
- }
2546
- function parsePasteInput() {
2547
- const lines = samples.value;
2548
- if (lines.length === 0) return;
2549
- const analysis = analyzeDelimiter(lines);
2550
- delimiter.value = analysis.delimiter;
2551
- dominantFieldCount.value = analysis.dominantFieldCount;
2552
- outliers.value = detectOutliers(lines, analysis.delimiter, analysis.dominantFieldCount);
2553
- for (const outlier of outliers.value) outlier.action = classifyOutlierAction(outlier.sample, analysis.delimiter);
2554
- const conforming = lines.filter((_, i) => !outliers.value.some((o) => o.index === i));
2555
- const conformingFieldCounts = conforming.map((s) => s.split(analysis.delimiter).length);
2556
- minFieldCount.value = conformingFieldCounts.length > 0 ? Math.min(...conformingFieldCounts) : analysis.dominantFieldCount;
2557
- fields.value = extractColumns(conforming, analysis.delimiter, minFieldCount.value);
2558
- fieldNames.value = {};
2559
- const rowCount = conforming.length;
2560
- enabledFields.value = new Set(fields.value.filter((f) => isUsefulField(f, rowCount)).map((f) => f.index));
2561
- }
2562
- function parseCsvInput() {
2563
- if (!csvData.value) return;
2564
- const csv = csvData.value;
2565
- fields.value = csv.columns.filter((c) => c !== csv.sampleColumn).map((col, i) => {
2566
- const values = csv.rows.map((r) => r[col]);
2567
- const unique = [...new Set(values)];
2568
- return {
2569
- index: i,
2570
- name: col,
2571
- originalName: col,
2572
- uniqueValues: unique,
2573
- cardinality: unique.length
2574
- };
2575
- });
2576
- outliers.value = [];
2577
- delimiter.value = csv.delimiter;
2578
- dominantFieldCount.value = csv.columns.length;
2579
- fieldNames.value = Object.fromEntries(fields.value.map((f) => [f.index, f.name]));
2580
- const rowCount = csv.rows.length;
2581
- enabledFields.value = new Set(fields.value.filter((f) => isUsefulField(f, rowCount)).map((f) => f.index));
2582
- }
2583
- function setOutlierAction(index, action) {
2584
- const outlier = outliers.value.find((o) => o.index === index);
2585
- if (outlier) {
2586
- outlier.action = action;
2587
- outliers.value = [...outliers.value];
3285
+ function setOutlierAction(_, __) {
3286
+ if (!outlierDeprecationWarned) {
3287
+ if (typeof process === "undefined" || process.env.NODE_ENV !== "production") {
3288
+ console.warn("[useAutoGroup] setOutlierAction is deprecated AND inactive in the class-first redesign. Use setClassDisposition('iqc' | 'eqc' | …, 'group' | 'overlay' | 'exclude') instead.");
3289
+ outlierDeprecationWarned = true;
3290
+ }
2588
3291
  }
2589
3292
  }
2590
- function setAllOutlierActions(action) {
2591
- for (const outlier of outliers.value) outlier.action = action;
2592
- outliers.value = [...outliers.value];
3293
+ function setAllOutlierActions(__) {
3294
+ if (!outlierDeprecationWarned) {
3295
+ if (typeof process === "undefined" || process.env.NODE_ENV !== "production") {
3296
+ console.warn("[useAutoGroup] setAllOutlierActions is deprecated AND inactive in the class-first redesign. Use setClassDisposition('iqc' | 'eqc' | …, 'group' | 'overlay' | 'exclude') instead.");
3297
+ outlierDeprecationWarned = true;
3298
+ }
3299
+ }
2593
3300
  }
2594
3301
  function toggleField(index) {
2595
- const newSet = new Set(enabledFields.value);
2596
- if (newSet.has(index)) newSet.delete(index);
2597
- else newSet.add(index);
2598
- enabledFields.value = newSet;
3302
+ warnDeprecated("toggleField");
3303
+ if (activeClassKey.value) toggleGroupBy(activeClassKey.value, index);
2599
3304
  }
2600
3305
  function renameField(index, name) {
2601
- fieldNames.value = {
2602
- ...fieldNames.value,
2603
- [index]: name
2604
- };
3306
+ warnDeprecated("renameField");
3307
+ if (activeClassKey.value) setColumnName(activeClassKey.value, index, name);
2605
3308
  }
2606
3309
  function loadExperimentData(rawData) {
2607
3310
  const parsed = extractSamplesFromDesignData(rawData);
2608
3311
  if (!parsed) return false;
2609
3312
  inputMode.value = "experiment";
2610
3313
  csvData.value = parsed;
2611
- parseCsvInput();
3314
+ sampleTypeHints.value = parsed.sampleTypeHints;
3315
+ parseInput();
2612
3316
  return true;
2613
3317
  }
2614
3318
  function reset() {
2615
3319
  rawText.value = "";
2616
3320
  csvData.value = null;
2617
- delimiter.value = "_";
2618
- dominantFieldCount.value = 1;
2619
- minFieldCount.value = 1;
3321
+ classes.value = [];
3322
+ schemas.value = {};
3323
+ activeClassKey.value = "";
3324
+ tokenized.value = [];
2620
3325
  outliers.value = [];
2621
- fields.value = [];
2622
- fieldNames.value = {};
2623
- enabledFields.value = /* @__PURE__ */ new Set();
3326
+ sampleTypeHints.value = [];
2624
3327
  }
2625
3328
  return {
2626
3329
  inputMode,
2627
3330
  rawText,
2628
3331
  csvData,
3332
+ sampleTypeHints,
3333
+ classes,
3334
+ schemas,
3335
+ activeClassKey,
2629
3336
  delimiter,
2630
3337
  dominantFieldCount,
2631
3338
  minFieldCount,
2632
- outliers,
2633
- fields,
2634
- fieldNames,
2635
- enabledFields,
2636
3339
  samples,
2637
- hasOutliers,
2638
- conformingSamples,
3340
+ tokenized,
3341
+ activeSchema,
3342
+ suggestions,
2639
3343
  groups,
3344
+ qcGroups,
2640
3345
  metadata,
2641
3346
  excludedSamples,
2642
- allSingletons,
2643
3347
  result,
3348
+ fingerprint,
3349
+ fields,
3350
+ fieldNames,
3351
+ enabledFields,
2644
3352
  effectiveColumns,
3353
+ hasOutliers,
3354
+ outliers,
3355
+ conformingSamples,
3356
+ allSingletons,
3357
+ canDownloadTemplate,
3358
+ setClassDisposition,
3359
+ setClassDispositions,
3360
+ toggleGroupBy,
3361
+ setRole,
3362
+ setColumnName,
3363
+ setValueOps,
3364
+ setBinning,
3365
+ mergeColumns,
3366
+ loadFingerprint,
2645
3367
  parseInput,
3368
+ downloadTemplate,
3369
+ composeTemplateForTest,
2646
3370
  loadExperimentData,
3371
+ reset,
2647
3372
  setOutlierAction,
2648
3373
  setAllOutlierActions,
2649
3374
  toggleField,
2650
- renameField,
2651
- reset
3375
+ renameField
3376
+ };
3377
+ }
3378
+ function extractSamplesFromDesignData(rawData) {
3379
+ const designData = unwrapExperimentDesignData(rawData);
3380
+ if (!designData) return null;
3381
+ const samples = designData.samples;
3382
+ if (!Array.isArray(samples) || samples.length === 0) return null;
3383
+ const allConditionKeys = [];
3384
+ const keySet = /* @__PURE__ */ new Set();
3385
+ const sampleTypeHints = [];
3386
+ for (const sample of samples) {
3387
+ const rawType = sample.sample_type;
3388
+ sampleTypeHints.push(typeof rawType === "string" ? rawType.toLowerCase() : void 0);
3389
+ const conditions = sample.conditions;
3390
+ if (conditions && typeof conditions === "object") {
3391
+ for (const key of Object.keys(conditions)) if (!keySet.has(key)) {
3392
+ keySet.add(key);
3393
+ allConditionKeys.push(key);
3394
+ }
3395
+ }
3396
+ }
3397
+ if (allConditionKeys.length === 0) return null;
3398
+ return {
3399
+ columns: [
3400
+ "sample_name",
3401
+ ...allConditionKeys,
3402
+ "sample_type"
3403
+ ],
3404
+ rows: samples.map((s) => {
3405
+ const conditions = s.conditions ?? {};
3406
+ const row = {
3407
+ sample_name: String(s.sample_name ?? ""),
3408
+ sample_type: String(s.sample_type ?? "sample")
3409
+ };
3410
+ for (const key of allConditionKeys) row[key] = conditions[key] ?? "";
3411
+ return row;
3412
+ }),
3413
+ sampleColumn: "sample_name",
3414
+ delimiter: ",",
3415
+ sampleTypeHints
2652
3416
  };
2653
3417
  }
2654
3418
  //#endregion
@@ -4196,8 +4960,8 @@ function useReagentSeries() {
4196
4960
  };
4197
4961
  }
4198
4962
  //#endregion
4199
- //#region src/composables/useProtocolTemplates.ts
4200
- var BUILT_IN_TEMPLATES = [
4963
+ //#region src/composables/protocolTemplateCatalog.ts
4964
+ var BUILT_IN_PROTOCOL_TEMPLATES = [
4201
4965
  {
4202
4966
  id: "builtin-incubation",
4203
4967
  type: "incubation",
@@ -4537,6 +5301,8 @@ var BUILT_IN_TEMPLATES = [
4537
5301
  }]
4538
5302
  }
4539
5303
  ];
5304
+ //#endregion
5305
+ //#region src/composables/useProtocolTemplates.ts
4540
5306
  var STORAGE_KEY = "mint-custom-protocol-templates";
4541
5307
  function isStepTemplate(value) {
4542
5308
  if (!value || typeof value !== "object") return false;
@@ -4562,13 +5328,13 @@ function saveCustomTemplatesToStorage(templates) {
4562
5328
  function useProtocolTemplates() {
4563
5329
  const customTemplates = ref(loadCustomTemplates());
4564
5330
  const allTemplates = computed(() => {
4565
- return [...BUILT_IN_TEMPLATES, ...customTemplates.value];
5331
+ return [...BUILT_IN_PROTOCOL_TEMPLATES, ...customTemplates.value];
4566
5332
  });
4567
5333
  function getTemplateByType(type) {
4568
- return customTemplates.value.find((t) => t.type === type) || BUILT_IN_TEMPLATES.find((t) => t.type === type);
5334
+ return customTemplates.value.find((t) => t.type === type) || BUILT_IN_PROTOCOL_TEMPLATES.find((t) => t.type === type);
4569
5335
  }
4570
5336
  function getTemplateById(id) {
4571
- return customTemplates.value.find((t) => t.id === id) || BUILT_IN_TEMPLATES.find((t) => t.id === id);
5337
+ return customTemplates.value.find((t) => t.id === id) || BUILT_IN_PROTOCOL_TEMPLATES.find((t) => t.id === id);
4572
5338
  }
4573
5339
  function saveCustomTemplate(template) {
4574
5340
  const newTemplate = {
@@ -4643,7 +5409,7 @@ function useProtocolTemplates() {
4643
5409
  return strValue;
4644
5410
  }
4645
5411
  return {
4646
- builtInTemplates: BUILT_IN_TEMPLATES,
5412
+ builtInTemplates: BUILT_IN_PROTOCOL_TEMPLATES,
4647
5413
  customTemplates,
4648
5414
  allTemplates,
4649
5415
  getTemplateByType,
@@ -4656,6 +5422,6 @@ function useProtocolTemplates() {
4656
5422
  };
4657
5423
  }
4658
5424
  //#endregion
4659
- export { usePlatformContext as $, parseCSV as A, useRequestSyncState as B, useExpansionSet as C, hslToHex as D, hexToHsl as E, useWellPlateEditor as F, EXPERIMENT_STATUS_OPTIONS as G, useApi as H, useDoseCalculator as I, datePresetToISO as J, EXPERIMENT_STATUS_VARIANT_MAP as K, APP_EXPERIMENT_KEY as L, extractSampleNamesFromDesignData as M, extractSampleOptionsFromDesignData as N, DEFAULT_COLORS as O, unwrapExperimentDesignData as P, resolveExperimentCode as Q, useAppExperiment as R, resolveCurrentExperimentId as S, deriveShade as T, DATE_PRESET_OPTIONS as U, useDebouncedWatch as V, EXPERIMENT_STATUS_LABELS as W, formatExperimentStatus as X, formatExperimentDate as Y, getExperimentStatusVariant as Z, useScheduleDrag as _, useReagentSeries as a, candidateMatchesSearch as at, currentExperimentFromContext as b, useBioTemplatePresetWorkspace as c, compareSortValues as ct, getBioTemplateComponentProps as d, evaluateCondition as et, toBioTemplateComponentPropsByComponent as f, useExperimentSave as g, useTemplateCollection as h, generateDilutionSeries as i, useToast as it, useAutoGroup as j, extractSamplesFromDesignData as k, useBioTemplatePackWorkspace as l, useSortedItems as lt, useBioTemplateControls as m, DEFAULT_PRESETS as n, useForm as nt, useGroupAssignment as o, normalizeSearchQuery as ot, useBioTemplateComponents as p, SORT_OPTIONS as q, DEFAULT_UNITS as r, useTheme as rt, useRackEditor as s, useTextSearch as st, useProtocolTemplates as t, useFormBuilder as tt, useBioTemplateWorkspace as u, useExperimentSamples as v, useSampleGroups as w, getInjectedPlatformContext as x, useExperimentData as y, useExperimentSelector as z };
5425
+ export { resolveExperimentCode as $, parseCSV as A, useExperimentSelector as B, useExpansionSet as C, hslToHex as D, hexToHsl as E, classKey as F, EXPERIMENT_STATUS_LABELS as G, useDebouncedWatch as H, useWellPlateEditor as I, SORT_OPTIONS as J, EXPERIMENT_STATUS_OPTIONS as K, useDoseCalculator as L, extractSampleOptionsFromDesignData as M, unwrapExperimentDesignData as N, extractSamplesFromDesignData as O, DEFAULT_COLORS as P, getExperimentStatusVariant as Q, APP_EXPERIMENT_KEY as R, resolveCurrentExperimentId as S, deriveShade as T, useApi as U, useRequestSyncState as V, DATE_PRESET_OPTIONS as W, formatExperimentDate as X, datePresetToISO as Y, formatExperimentStatus as Z, useScheduleDrag as _, useReagentSeries as a, useToast as at, currentExperimentFromContext as b, useBioTemplatePresetWorkspace as c, useTextSearch as ct, getBioTemplateComponentProps as d, usePlatformContext as et, toBioTemplateComponentPropsByComponent as f, useExperimentSave as g, useTemplateCollection as h, generateDilutionSeries as i, useTheme as it, extractSampleNamesFromDesignData as j, useAutoGroup as k, useBioTemplatePackWorkspace as l, compareSortValues as lt, useBioTemplateControls as m, DEFAULT_PRESETS as n, evaluateCondition as nt, useGroupAssignment as o, candidateMatchesSearch as ot, useBioTemplateComponents as p, EXPERIMENT_STATUS_VARIANT_MAP as q, DEFAULT_UNITS as r, useForm as rt, useRackEditor as s, normalizeSearchQuery as st, useProtocolTemplates as t, useFormBuilder as tt, useBioTemplateWorkspace as u, useSortedItems as ut, useExperimentSamples as v, useSampleGroups as w, getInjectedPlatformContext as x, useExperimentData as y, useAppExperiment as z };
4660
5426
 
4661
- //# sourceMappingURL=useProtocolTemplates-n6AJqSqv.js.map
5427
+ //# sourceMappingURL=useProtocolTemplates-Bm5vyH4_.js.map