@morscherlab/mint-sdk 1.0.0-alpha.2

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 (491) hide show
  1. package/README.md +326 -0
  2. package/dist/__stories__/experiment-helpers.d.ts +25 -0
  3. package/dist/__tests__/components/AppLayout.test.d.ts +1 -0
  4. package/dist/__tests__/components/AppSidebar.test.d.ts +1 -0
  5. package/dist/__tests__/components/AppTopBar.test.d.ts +1 -0
  6. package/dist/__tests__/components/BaseInput.test.d.ts +1 -0
  7. package/dist/__tests__/components/BasePill.test.d.ts +1 -0
  8. package/dist/__tests__/components/Calendar.test.d.ts +1 -0
  9. package/dist/__tests__/components/CollapsibleCard.test.d.ts +1 -0
  10. package/dist/__tests__/components/DataFrame.test.d.ts +1 -0
  11. package/dist/__tests__/components/DropdownButton.test.d.ts +1 -0
  12. package/dist/__tests__/composables/formBuilderRegistry.test.d.ts +1 -0
  13. package/dist/__tests__/composables/useAppExperiment.test.d.ts +1 -0
  14. package/dist/__tests__/composables/useAuth.test.d.ts +1 -0
  15. package/dist/__tests__/composables/useAutoGroup.test.d.ts +1 -0
  16. package/dist/__tests__/composables/useExperimentData.test.d.ts +13 -0
  17. package/dist/__tests__/composables/useExperimentSave.test.d.ts +1 -0
  18. package/dist/__tests__/composables/useForm.test.d.ts +1 -0
  19. package/dist/__tests__/composables/useFormBuilder.test.d.ts +1 -0
  20. package/dist/__tests__/composables/usePlatformContext.test.d.ts +1 -0
  21. package/dist/__tests__/composables/usePluginApi.test.d.ts +13 -0
  22. package/dist/__tests__/composables/usePluginConfig.test.d.ts +14 -0
  23. package/dist/__tests__/utils/color.test.d.ts +1 -0
  24. package/dist/auth-BYmxZdJl.js +297 -0
  25. package/dist/auth-BYmxZdJl.js.map +1 -0
  26. package/dist/components/AlertBox.vue.d.ts +34 -0
  27. package/dist/components/AppAvatarMenu.vue.d.ts +58 -0
  28. package/dist/components/AppContainer.vue.d.ts +28 -0
  29. package/dist/components/AppLayout.vue.d.ts +31 -0
  30. package/dist/components/AppPageSelector.vue.d.ts +43 -0
  31. package/dist/components/AppPillNav.vue.d.ts +11 -0
  32. package/dist/components/AppPluginSwitcher.vue.d.ts +38 -0
  33. package/dist/components/AppSidebar.vue.d.ts +47 -0
  34. package/dist/components/AppTopBar.vue.d.ts +111 -0
  35. package/dist/components/AuditTrail.vue.d.ts +38 -0
  36. package/dist/components/AutoGroupModal.vue.d.ts +124 -0
  37. package/dist/components/Avatar.vue.d.ts +14 -0
  38. package/dist/components/BaseButton.vue.d.ts +37 -0
  39. package/dist/components/BaseCheckbox.vue.d.ts +17 -0
  40. package/dist/components/BaseInput.vue.d.ts +34 -0
  41. package/dist/components/BaseModal.vue.d.ts +46 -0
  42. package/dist/components/BasePill.vue.d.ts +57 -0
  43. package/dist/components/BaseRadioGroup.vue.d.ts +21 -0
  44. package/dist/components/BaseSelect.vue.d.ts +20 -0
  45. package/dist/components/BaseSlider.vue.d.ts +22 -0
  46. package/dist/components/BaseTabs.vue.d.ts +14 -0
  47. package/dist/components/BaseTextarea.vue.d.ts +30 -0
  48. package/dist/components/BaseToggle.vue.d.ts +19 -0
  49. package/dist/components/BatchProgressList.vue.d.ts +43 -0
  50. package/dist/components/Breadcrumb.vue.d.ts +33 -0
  51. package/dist/components/Calendar.vue.d.ts +107 -0
  52. package/dist/components/ChartContainer.vue.d.ts +31 -0
  53. package/dist/components/ChemicalFormula.vue.d.ts +8 -0
  54. package/dist/components/CollapsibleCard.vue.d.ts +41 -0
  55. package/dist/components/ColorSlider.vue.d.ts +34 -0
  56. package/dist/components/ConcentrationInput.vue.d.ts +25 -0
  57. package/dist/components/ConfirmDialog.vue.d.ts +42 -0
  58. package/dist/components/DataFrame.vue.d.ts +107 -0
  59. package/dist/components/DatePicker.vue.d.ts +25 -0
  60. package/dist/components/DateTimePicker.vue.d.ts +30 -0
  61. package/dist/components/Divider.vue.d.ts +14 -0
  62. package/dist/components/DoseCalculator.vue.d.ts +19 -0
  63. package/dist/components/DropdownButton.vue.d.ts +47 -0
  64. package/dist/components/EmptyState.vue.d.ts +36 -0
  65. package/dist/components/ExperimentCodeBadge.vue.d.ts +14 -0
  66. package/dist/components/ExperimentDataViewer.vue.d.ts +29 -0
  67. package/dist/components/ExperimentPopover.vue.d.ts +32 -0
  68. package/dist/components/ExperimentSelectorModal.vue.d.ts +28 -0
  69. package/dist/components/ExperimentTimeline.vue.d.ts +44 -0
  70. package/dist/components/FileUploader.vue.d.ts +40 -0
  71. package/dist/components/FitPanel.vue.d.ts +46 -0
  72. package/dist/components/FormActions.vue.d.ts +33 -0
  73. package/dist/components/FormBuilder.vue.d.ts +287 -0
  74. package/dist/components/FormField.vue.d.ts +28 -0
  75. package/dist/components/FormFieldRenderer.vue.d.ts +31 -0
  76. package/dist/components/FormSection.vue.d.ts +43 -0
  77. package/dist/components/FormulaInput.vue.d.ts +25 -0
  78. package/dist/components/GroupAssigner.vue.d.ts +25 -0
  79. package/dist/components/GroupingModal.vue.d.ts +12 -0
  80. package/dist/components/IconButton.vue.d.ts +34 -0
  81. package/dist/components/LoadingSpinner.vue.d.ts +12 -0
  82. package/dist/components/MoleculeInput.vue.d.ts +27 -0
  83. package/dist/components/MultiSelect.vue.d.ts +19 -0
  84. package/dist/components/NumberInput.vue.d.ts +22 -0
  85. package/dist/components/PlateMapEditor.vue.d.ts +50 -0
  86. package/dist/components/ProgressBar.vue.d.ts +23 -0
  87. package/dist/components/ProtocolStepEditor.vue.d.ts +24 -0
  88. package/dist/components/RackEditor.vue.d.ts +40 -0
  89. package/dist/components/ReagentEditor.vue.d.ts +30 -0
  90. package/dist/components/ReagentList.vue.d.ts +32 -0
  91. package/dist/components/ResourceCard.vue.d.ts +50 -0
  92. package/dist/components/SampleHierarchyTree.vue.d.ts +26 -0
  93. package/dist/components/SampleLegend.vue.d.ts +32 -0
  94. package/dist/components/SampleSelector.vue.d.ts +29 -0
  95. package/dist/components/ScheduleCalendar.vue.d.ts +110 -0
  96. package/dist/components/ScientificNumber.vue.d.ts +14 -0
  97. package/dist/components/SegmentedControl.vue.d.ts +20 -0
  98. package/dist/components/SequenceInput.vue.d.ts +54 -0
  99. package/dist/components/SettingsButton.vue.d.ts +30 -0
  100. package/dist/components/SettingsModal.vue.d.ts +36 -0
  101. package/dist/components/Skeleton.vue.d.ts +11 -0
  102. package/dist/components/StatusIndicator.vue.d.ts +13 -0
  103. package/dist/components/StepWizard.vue.d.ts +65 -0
  104. package/dist/components/TagsInput.vue.d.ts +39 -0
  105. package/dist/components/ThemeToggle.vue.d.ts +7 -0
  106. package/dist/components/TimePicker.vue.d.ts +29 -0
  107. package/dist/components/TimeRangeInput.vue.d.ts +27 -0
  108. package/dist/components/ToastNotification.vue.d.ts +2 -0
  109. package/dist/components/Tooltip.vue.d.ts +35 -0
  110. package/dist/components/UnitInput.vue.d.ts +39 -0
  111. package/dist/components/WellEditPopup.vue.d.ts +25 -0
  112. package/dist/components/WellPlate.vue.d.ts +73 -0
  113. package/dist/components/index.d.ts +87 -0
  114. package/dist/components/index.js +3 -0
  115. package/dist/components-CKf-UpGi.js +15089 -0
  116. package/dist/components-CKf-UpGi.js.map +1 -0
  117. package/dist/composables/experiment-utils.d.ts +8 -0
  118. package/dist/composables/formBuilderRegistry.d.ts +13 -0
  119. package/dist/composables/index.d.ts +28 -0
  120. package/dist/composables/index.js +3 -0
  121. package/dist/composables/useApi.d.ts +20 -0
  122. package/dist/composables/useAppExperiment.d.ts +37 -0
  123. package/dist/composables/useAsync.d.ts +128 -0
  124. package/dist/composables/useAuth.d.ts +47 -0
  125. package/dist/composables/useAutoGroup.d.ts +106 -0
  126. package/dist/composables/useChemicalFormula.d.ts +21 -0
  127. package/dist/composables/useConcentrationUnits.d.ts +29 -0
  128. package/dist/composables/useDoseCalculator.d.ts +58 -0
  129. package/dist/composables/useExperimentData.d.ts +18 -0
  130. package/dist/composables/useExperimentSave.d.ts +36 -0
  131. package/dist/composables/useExperimentSelector.d.ts +30 -0
  132. package/dist/composables/useForm.d.ts +92 -0
  133. package/dist/composables/useFormBuilder.d.ts +24 -0
  134. package/dist/composables/usePasskey.d.ts +10 -0
  135. package/dist/composables/usePlatformContext.d.ts +131 -0
  136. package/dist/composables/usePluginApi.d.ts +29 -0
  137. package/dist/composables/usePluginConfig.d.ts +13 -0
  138. package/dist/composables/useProtocolTemplates.d.ts +44 -0
  139. package/dist/composables/useRackEditor.d.ts +31 -0
  140. package/dist/composables/useReagentSeries.d.ts +23 -0
  141. package/dist/composables/useScheduleDrag.d.ts +78 -0
  142. package/dist/composables/useSequenceUtils.d.ts +14 -0
  143. package/dist/composables/useTheme.d.ts +8 -0
  144. package/dist/composables/useTimeUtils.d.ts +29 -0
  145. package/dist/composables/useToast.d.ts +22 -0
  146. package/dist/composables/useWellPlateEditor.d.ts +33 -0
  147. package/dist/composables-D0QfFzq1.js +805 -0
  148. package/dist/composables-D0QfFzq1.js.map +1 -0
  149. package/dist/histoire.setup.d.ts +1 -0
  150. package/dist/index.d.ts +6 -0
  151. package/dist/index.js +7 -0
  152. package/dist/install.d.ts +16 -0
  153. package/dist/install.js +23 -0
  154. package/dist/install.js.map +1 -0
  155. package/dist/stores/auth.d.ts +146 -0
  156. package/dist/stores/index.d.ts +2 -0
  157. package/dist/stores/index.js +2 -0
  158. package/dist/stores/settings.d.ts +75 -0
  159. package/dist/styles.css +29728 -0
  160. package/dist/tailwind.preset.d.ts +58 -0
  161. package/dist/tailwind.preset.js +66 -0
  162. package/dist/tailwind.preset.js.map +1 -0
  163. package/dist/types/auth.d.ts +42 -0
  164. package/dist/types/auto-group.d.ts +34 -0
  165. package/dist/types/components.d.ts +528 -0
  166. package/dist/types/form-builder.d.ts +167 -0
  167. package/dist/types/index.d.ts +5 -0
  168. package/dist/types/index.js +0 -0
  169. package/dist/types/platform.d.ts +75 -0
  170. package/dist/useScheduleDrag-DAJueTbK.js +7181 -0
  171. package/dist/useScheduleDrag-DAJueTbK.js.map +1 -0
  172. package/dist/utils/color.d.ts +24 -0
  173. package/package.json +114 -0
  174. package/src/__stories__/experiment-helpers.ts +83 -0
  175. package/src/__tests__/components/AppLayout.test.ts +163 -0
  176. package/src/__tests__/components/AppSidebar.test.ts +292 -0
  177. package/src/__tests__/components/AppTopBar.test.ts +683 -0
  178. package/src/__tests__/components/BaseInput.test.ts +99 -0
  179. package/src/__tests__/components/BasePill.test.ts +291 -0
  180. package/src/__tests__/components/Calendar.test.ts +566 -0
  181. package/src/__tests__/components/CollapsibleCard.test.ts +524 -0
  182. package/src/__tests__/components/DataFrame.test.ts +767 -0
  183. package/src/__tests__/components/DropdownButton.test.ts +471 -0
  184. package/src/__tests__/composables/formBuilderRegistry.test.ts +187 -0
  185. package/src/__tests__/composables/useAppExperiment.test.ts +560 -0
  186. package/src/__tests__/composables/useAuth.test.ts +188 -0
  187. package/src/__tests__/composables/useAutoGroup.test.ts +860 -0
  188. package/src/__tests__/composables/useExperimentData.test.ts +127 -0
  189. package/src/__tests__/composables/useExperimentSave.test.ts +347 -0
  190. package/src/__tests__/composables/useForm.test.ts +205 -0
  191. package/src/__tests__/composables/useFormBuilder.test.ts +917 -0
  192. package/src/__tests__/composables/usePlatformContext.test.ts +116 -0
  193. package/src/__tests__/composables/usePluginApi.test.ts +81 -0
  194. package/src/__tests__/composables/usePluginConfig.test.ts +176 -0
  195. package/src/__tests__/utils/color.test.ts +96 -0
  196. package/src/components/AlertBox.story.vue +204 -0
  197. package/src/components/AlertBox.vue +88 -0
  198. package/src/components/AppAvatarMenu.story.vue +155 -0
  199. package/src/components/AppAvatarMenu.vue +184 -0
  200. package/src/components/AppContainer.story.vue +104 -0
  201. package/src/components/AppContainer.vue +34 -0
  202. package/src/components/AppLayout.story.vue +292 -0
  203. package/src/components/AppLayout.vue +75 -0
  204. package/src/components/AppPageSelector.vue +159 -0
  205. package/src/components/AppPillNav.vue +66 -0
  206. package/src/components/AppPluginSwitcher.vue +241 -0
  207. package/src/components/AppSidebar.story.vue +309 -0
  208. package/src/components/AppSidebar.vue +119 -0
  209. package/src/components/AppTopBar.story.vue +304 -0
  210. package/src/components/AppTopBar.vue +661 -0
  211. package/src/components/AuditTrail.story.vue +163 -0
  212. package/src/components/AuditTrail.vue +151 -0
  213. package/src/components/AutoGroupModal.story.vue +273 -0
  214. package/src/components/AutoGroupModal.vue +566 -0
  215. package/src/components/Avatar.story.vue +115 -0
  216. package/src/components/Avatar.vue +79 -0
  217. package/src/components/BaseButton.story.vue +96 -0
  218. package/src/components/BaseButton.vue +73 -0
  219. package/src/components/BaseCheckbox.story.vue +73 -0
  220. package/src/components/BaseCheckbox.vue +69 -0
  221. package/src/components/BaseInput.story.vue +98 -0
  222. package/src/components/BaseInput.vue +74 -0
  223. package/src/components/BaseModal.story.vue +237 -0
  224. package/src/components/BaseModal.vue +182 -0
  225. package/src/components/BasePill.story.vue +142 -0
  226. package/src/components/BasePill.vue +89 -0
  227. package/src/components/BaseRadioGroup.story.vue +145 -0
  228. package/src/components/BaseRadioGroup.vue +124 -0
  229. package/src/components/BaseSelect.story.vue +120 -0
  230. package/src/components/BaseSelect.vue +71 -0
  231. package/src/components/BaseSlider.story.vue +122 -0
  232. package/src/components/BaseSlider.vue +126 -0
  233. package/src/components/BaseTabs.story.vue +127 -0
  234. package/src/components/BaseTabs.vue +59 -0
  235. package/src/components/BaseTextarea.story.vue +91 -0
  236. package/src/components/BaseTextarea.vue +62 -0
  237. package/src/components/BaseToggle.story.vue +81 -0
  238. package/src/components/BaseToggle.vue +76 -0
  239. package/src/components/BatchProgressList.story.vue +92 -0
  240. package/src/components/BatchProgressList.vue +184 -0
  241. package/src/components/Breadcrumb.story.vue +106 -0
  242. package/src/components/Breadcrumb.vue +75 -0
  243. package/src/components/Calendar.story.vue +106 -0
  244. package/src/components/Calendar.vue +363 -0
  245. package/src/components/ChartContainer.story.vue +113 -0
  246. package/src/components/ChartContainer.vue +64 -0
  247. package/src/components/ChemicalFormula.story.vue +102 -0
  248. package/src/components/ChemicalFormula.vue +39 -0
  249. package/src/components/CollapsibleCard.story.vue +135 -0
  250. package/src/components/CollapsibleCard.vue +167 -0
  251. package/src/components/ColorSlider.story.vue +120 -0
  252. package/src/components/ColorSlider.vue +164 -0
  253. package/src/components/ConcentrationInput.story.vue +77 -0
  254. package/src/components/ConcentrationInput.vue +185 -0
  255. package/src/components/ConfirmDialog.story.vue +248 -0
  256. package/src/components/ConfirmDialog.vue +93 -0
  257. package/src/components/DataFrame.story.vue +148 -0
  258. package/src/components/DataFrame.vue +419 -0
  259. package/src/components/DatePicker.story.vue +119 -0
  260. package/src/components/DatePicker.vue +330 -0
  261. package/src/components/DateTimePicker.story.vue +112 -0
  262. package/src/components/DateTimePicker.vue +392 -0
  263. package/src/components/Divider.story.vue +80 -0
  264. package/src/components/Divider.vue +49 -0
  265. package/src/components/DoseCalculator.story.vue +68 -0
  266. package/src/components/DoseCalculator.vue +476 -0
  267. package/src/components/DropdownButton.story.vue +102 -0
  268. package/src/components/DropdownButton.vue +181 -0
  269. package/src/components/EmptyState.story.vue +135 -0
  270. package/src/components/EmptyState.vue +69 -0
  271. package/src/components/ExperimentCodeBadge.story.vue +77 -0
  272. package/src/components/ExperimentCodeBadge.vue +64 -0
  273. package/src/components/ExperimentDataViewer.story.vue +174 -0
  274. package/src/components/ExperimentDataViewer.vue +288 -0
  275. package/src/components/ExperimentPopover.story.vue +384 -0
  276. package/src/components/ExperimentPopover.vue +241 -0
  277. package/src/components/ExperimentSelectorModal.story.vue +391 -0
  278. package/src/components/ExperimentSelectorModal.vue +387 -0
  279. package/src/components/ExperimentTimeline.story.vue +161 -0
  280. package/src/components/ExperimentTimeline.vue +382 -0
  281. package/src/components/FileUploader.story.vue +107 -0
  282. package/src/components/FileUploader.vue +386 -0
  283. package/src/components/FitPanel.story.vue +125 -0
  284. package/src/components/FitPanel.vue +120 -0
  285. package/src/components/FormActions.vue +92 -0
  286. package/src/components/FormBuilder.vue +214 -0
  287. package/src/components/FormField.story.vue +132 -0
  288. package/src/components/FormField.vue +59 -0
  289. package/src/components/FormFieldRenderer.vue +58 -0
  290. package/src/components/FormSection.vue +90 -0
  291. package/src/components/FormulaInput.story.vue +96 -0
  292. package/src/components/FormulaInput.vue +125 -0
  293. package/src/components/GroupAssigner.story.vue +83 -0
  294. package/src/components/GroupAssigner.vue +284 -0
  295. package/src/components/GroupingModal.story.vue +52 -0
  296. package/src/components/GroupingModal.vue +422 -0
  297. package/src/components/IconButton.story.vue +135 -0
  298. package/src/components/IconButton.vue +73 -0
  299. package/src/components/LoadingSpinner.story.vue +70 -0
  300. package/src/components/LoadingSpinner.vue +50 -0
  301. package/src/components/MoleculeInput.story.vue +66 -0
  302. package/src/components/MoleculeInput.vue +426 -0
  303. package/src/components/MultiSelect.story.vue +132 -0
  304. package/src/components/MultiSelect.vue +118 -0
  305. package/src/components/NumberInput.story.vue +122 -0
  306. package/src/components/NumberInput.vue +160 -0
  307. package/src/components/PlateMapEditor.story.vue +92 -0
  308. package/src/components/PlateMapEditor.vue +513 -0
  309. package/src/components/ProgressBar.story.vue +148 -0
  310. package/src/components/ProgressBar.vue +114 -0
  311. package/src/components/ProtocolStepEditor.story.vue +69 -0
  312. package/src/components/ProtocolStepEditor.vue +522 -0
  313. package/src/components/RackEditor.story.vue +100 -0
  314. package/src/components/RackEditor.vue +371 -0
  315. package/src/components/ReagentEditor.story.vue +153 -0
  316. package/src/components/ReagentEditor.vue +418 -0
  317. package/src/components/ReagentList.story.vue +137 -0
  318. package/src/components/ReagentList.vue +463 -0
  319. package/src/components/ResourceCard.story.vue +150 -0
  320. package/src/components/ResourceCard.vue +161 -0
  321. package/src/components/SampleHierarchyTree.story.vue +161 -0
  322. package/src/components/SampleHierarchyTree.vue +256 -0
  323. package/src/components/SampleLegend.story.vue +91 -0
  324. package/src/components/SampleLegend.vue +119 -0
  325. package/src/components/SampleSelector.story.vue +111 -0
  326. package/src/components/SampleSelector.vue +1033 -0
  327. package/src/components/ScheduleCalendar.story.vue +195 -0
  328. package/src/components/ScheduleCalendar.vue +569 -0
  329. package/src/components/ScientificNumber.story.vue +127 -0
  330. package/src/components/ScientificNumber.vue +197 -0
  331. package/src/components/SegmentedControl.story.vue +132 -0
  332. package/src/components/SegmentedControl.vue +79 -0
  333. package/src/components/SequenceInput.story.vue +119 -0
  334. package/src/components/SequenceInput.vue +209 -0
  335. package/src/components/SettingsButton.story.vue +58 -0
  336. package/src/components/SettingsButton.vue +76 -0
  337. package/src/components/SettingsModal.story.vue +145 -0
  338. package/src/components/SettingsModal.vue +146 -0
  339. package/src/components/Skeleton.story.vue +141 -0
  340. package/src/components/Skeleton.vue +74 -0
  341. package/src/components/StatusIndicator.story.vue +99 -0
  342. package/src/components/StatusIndicator.vue +40 -0
  343. package/src/components/StepWizard.story.vue +155 -0
  344. package/src/components/StepWizard.vue +223 -0
  345. package/src/components/TagsInput.story.vue +155 -0
  346. package/src/components/TagsInput.vue +265 -0
  347. package/src/components/ThemeToggle.story.vue +36 -0
  348. package/src/components/ThemeToggle.vue +54 -0
  349. package/src/components/TimePicker.story.vue +96 -0
  350. package/src/components/TimePicker.vue +273 -0
  351. package/src/components/TimeRangeInput.story.vue +104 -0
  352. package/src/components/TimeRangeInput.vue +122 -0
  353. package/src/components/ToastNotification.story.vue +157 -0
  354. package/src/components/ToastNotification.vue +62 -0
  355. package/src/components/Tooltip.story.vue +138 -0
  356. package/src/components/Tooltip.vue +119 -0
  357. package/src/components/UnitInput.story.vue +194 -0
  358. package/src/components/UnitInput.vue +213 -0
  359. package/src/components/WellEditPopup.vue +234 -0
  360. package/src/components/WellPlate.story.vue +282 -0
  361. package/src/components/WellPlate.vue +830 -0
  362. package/src/components/index.ts +118 -0
  363. package/src/composables/experiment-utils.ts +57 -0
  364. package/src/composables/formBuilderRegistry.ts +79 -0
  365. package/src/composables/index.ts +140 -0
  366. package/src/composables/useApi.ts +167 -0
  367. package/src/composables/useAppExperiment.ts +159 -0
  368. package/src/composables/useAsync.ts +323 -0
  369. package/src/composables/useAuth.ts +445 -0
  370. package/src/composables/useAutoGroup.ts +641 -0
  371. package/src/composables/useChemicalFormula.ts +275 -0
  372. package/src/composables/useConcentrationUnits.ts +246 -0
  373. package/src/composables/useDoseCalculator.ts +370 -0
  374. package/src/composables/useExperimentData.ts +86 -0
  375. package/src/composables/useExperimentSave.ts +192 -0
  376. package/src/composables/useExperimentSelector.ts +292 -0
  377. package/src/composables/useForm.ts +416 -0
  378. package/src/composables/useFormBuilder.ts +383 -0
  379. package/src/composables/usePasskey.ts +216 -0
  380. package/src/composables/usePlatformContext.ts +299 -0
  381. package/src/composables/usePluginApi.ts +39 -0
  382. package/src/composables/usePluginConfig.ts +93 -0
  383. package/src/composables/useProtocolTemplates.ts +518 -0
  384. package/src/composables/useRackEditor.ts +222 -0
  385. package/src/composables/useReagentSeries.ts +91 -0
  386. package/src/composables/useScheduleDrag.ts +245 -0
  387. package/src/composables/useSequenceUtils.ts +105 -0
  388. package/src/composables/useTheme.ts +58 -0
  389. package/src/composables/useTimeUtils.ts +131 -0
  390. package/src/composables/useToast.ts +40 -0
  391. package/src/composables/useWellPlateEditor.ts +421 -0
  392. package/src/histoire.setup.ts +17 -0
  393. package/src/index.ts +367 -0
  394. package/src/install.ts +32 -0
  395. package/src/stores/auth.ts +152 -0
  396. package/src/stores/index.ts +2 -0
  397. package/src/stores/settings.ts +218 -0
  398. package/src/styles/components/alert-box.css +150 -0
  399. package/src/styles/components/app-avatar-menu.css +155 -0
  400. package/src/styles/components/app-container.css +33 -0
  401. package/src/styles/components/app-layout.css +98 -0
  402. package/src/styles/components/app-page-selector.css +191 -0
  403. package/src/styles/components/app-pill-nav.css +57 -0
  404. package/src/styles/components/app-plugin-switcher.css +209 -0
  405. package/src/styles/components/app-sidebar.css +145 -0
  406. package/src/styles/components/app-top-bar.css +492 -0
  407. package/src/styles/components/audit-trail.css +143 -0
  408. package/src/styles/components/auto-group-modal.css +644 -0
  409. package/src/styles/components/avatar.css +73 -0
  410. package/src/styles/components/batch-progress-list.css +196 -0
  411. package/src/styles/components/breadcrumb.css +64 -0
  412. package/src/styles/components/button.css +188 -0
  413. package/src/styles/components/calendar.css +192 -0
  414. package/src/styles/components/chart-container.css +69 -0
  415. package/src/styles/components/checkbox.css +123 -0
  416. package/src/styles/components/chemical-formula.css +46 -0
  417. package/src/styles/components/collapsible-card.css +253 -0
  418. package/src/styles/components/color-slider.css +110 -0
  419. package/src/styles/components/concentration-input.css +156 -0
  420. package/src/styles/components/confirm-dialog.css +183 -0
  421. package/src/styles/components/dataframe.css +382 -0
  422. package/src/styles/components/date-picker.css +243 -0
  423. package/src/styles/components/datetime-picker.css +229 -0
  424. package/src/styles/components/divider.css +63 -0
  425. package/src/styles/components/dose-calculator.css +301 -0
  426. package/src/styles/components/dropdown-button.css +280 -0
  427. package/src/styles/components/empty-state.css +151 -0
  428. package/src/styles/components/experiment-code-badge.css +33 -0
  429. package/src/styles/components/experiment-data-viewer.css +138 -0
  430. package/src/styles/components/experiment-popover.css +562 -0
  431. package/src/styles/components/experiment-selector-modal.css +285 -0
  432. package/src/styles/components/experiment-timeline.css +529 -0
  433. package/src/styles/components/file-uploader.css +310 -0
  434. package/src/styles/components/fit-panel.css +67 -0
  435. package/src/styles/components/form-builder.css +69 -0
  436. package/src/styles/components/form-field.css +48 -0
  437. package/src/styles/components/formula-input.css +103 -0
  438. package/src/styles/components/group-assigner.css +200 -0
  439. package/src/styles/components/grouping-modal.css +323 -0
  440. package/src/styles/components/icon-button.css +192 -0
  441. package/src/styles/components/input.css +66 -0
  442. package/src/styles/components/loading-spinner.css +67 -0
  443. package/src/styles/components/modal.css +350 -0
  444. package/src/styles/components/molecule-input.css +186 -0
  445. package/src/styles/components/multi-select.css +131 -0
  446. package/src/styles/components/number-input.css +199 -0
  447. package/src/styles/components/pill.css +188 -0
  448. package/src/styles/components/plate-map-editor.css +464 -0
  449. package/src/styles/components/progress-bar.css +133 -0
  450. package/src/styles/components/protocol-step-editor.css +449 -0
  451. package/src/styles/components/rack-editor.css +265 -0
  452. package/src/styles/components/radio-group.css +240 -0
  453. package/src/styles/components/reagent-editor.css +510 -0
  454. package/src/styles/components/reagent-list.css +407 -0
  455. package/src/styles/components/resource-card.css +360 -0
  456. package/src/styles/components/sample-hierarchy-tree.css +314 -0
  457. package/src/styles/components/sample-legend.css +201 -0
  458. package/src/styles/components/sample-selector.css +751 -0
  459. package/src/styles/components/schedule-calendar.css +478 -0
  460. package/src/styles/components/scientific-number.css +63 -0
  461. package/src/styles/components/segmented-control.css +197 -0
  462. package/src/styles/components/select.css +77 -0
  463. package/src/styles/components/sequence-input.css +184 -0
  464. package/src/styles/components/settings-button.css +94 -0
  465. package/src/styles/components/settings-modal.css +95 -0
  466. package/src/styles/components/skeleton.css +49 -0
  467. package/src/styles/components/slider.css +74 -0
  468. package/src/styles/components/status-indicator.css +66 -0
  469. package/src/styles/components/step-wizard.css +192 -0
  470. package/src/styles/components/tabs.css +95 -0
  471. package/src/styles/components/tags-input.css +195 -0
  472. package/src/styles/components/textarea.css +82 -0
  473. package/src/styles/components/theme-toggle.css +69 -0
  474. package/src/styles/components/time-picker.css +171 -0
  475. package/src/styles/components/time-range-input.css +42 -0
  476. package/src/styles/components/toast.css +91 -0
  477. package/src/styles/components/toggle.css +146 -0
  478. package/src/styles/components/tooltip.css +91 -0
  479. package/src/styles/components/unit-input.css +123 -0
  480. package/src/styles/components/well-edit-popup.css +252 -0
  481. package/src/styles/components/well-plate.css +307 -0
  482. package/src/styles/index.css +87 -0
  483. package/src/styles/variables.css +1117 -0
  484. package/src/tailwind.preset.ts +61 -0
  485. package/src/types/auth.ts +55 -0
  486. package/src/types/auto-group.ts +40 -0
  487. package/src/types/components.ts +710 -0
  488. package/src/types/form-builder.ts +197 -0
  489. package/src/types/index.ts +207 -0
  490. package/src/types/platform.ts +116 -0
  491. package/src/utils/color.ts +96 -0
@@ -0,0 +1,641 @@
1
+ import { ref, computed } from 'vue'
2
+ import type {
3
+ InputMode,
4
+ OutlierAction,
5
+ OutlierInfo,
6
+ ColumnInfo,
7
+ MetadataRow,
8
+ AutoGroupResult,
9
+ ParsedCsvData,
10
+ } from '../types/auto-group'
11
+ import type { SampleGroup } from '../types/components'
12
+
13
+ export const DEFAULT_COLORS = [
14
+ '#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6',
15
+ '#EC4899', '#06B6D4', '#84CC16', '#F97316', '#6366F1',
16
+ ]
17
+
18
+ const DELIMITER_CANDIDATES = ['_', '-', '.'] as const
19
+
20
+ // --- Pure functions (exported for testing) ---
21
+
22
+ export function analyzeDelimiter(lines: string[]): {
23
+ delimiter: string
24
+ dominantFieldCount: number
25
+ minFieldCount: number
26
+ consistency: number
27
+ } {
28
+ if (lines.length === 0) {
29
+ return { delimiter: '_', dominantFieldCount: 1, minFieldCount: 1, consistency: 0 }
30
+ }
31
+
32
+ let bestDelimiter = '_'
33
+ let bestConsistency = -1
34
+ let bestFieldCount = 1
35
+
36
+ for (const candidate of DELIMITER_CANDIDATES) {
37
+ const fieldCounts = lines.map(line => line.split(candidate).length)
38
+ const countFrequency = new Map<number, number>()
39
+
40
+ for (const count of fieldCounts) {
41
+ countFrequency.set(count, (countFrequency.get(count) ?? 0) + 1)
42
+ }
43
+
44
+ // Find mode (most frequent field count)
45
+ let modeCount = 1
46
+ let modeFrequency = 0
47
+ for (const [count, freq] of countFrequency) {
48
+ if (freq > modeFrequency || (freq === modeFrequency && count > modeCount)) {
49
+ modeCount = count
50
+ modeFrequency = freq
51
+ }
52
+ }
53
+
54
+ const rawConsistency = modeFrequency / lines.length
55
+ // A delimiter that produces field count 1 didn't actually split anything
56
+ const consistency = modeCount > 1 ? rawConsistency : 0
57
+
58
+ if (
59
+ consistency > bestConsistency ||
60
+ (consistency === bestConsistency &&
61
+ DELIMITER_CANDIDATES.indexOf(candidate) < DELIMITER_CANDIDATES.indexOf(bestDelimiter as typeof candidate))
62
+ ) {
63
+ bestDelimiter = candidate
64
+ bestConsistency = consistency
65
+ bestFieldCount = modeCount
66
+ }
67
+ }
68
+
69
+ const bestFieldCounts = lines.map(line => line.split(bestDelimiter).length)
70
+ const multiFieldCounts = bestFieldCounts.filter(c => c >= 2)
71
+ const minFieldCount = multiFieldCounts.length > 0 ? Math.min(...multiFieldCounts) : 1
72
+
73
+ return {
74
+ delimiter: bestDelimiter,
75
+ dominantFieldCount: bestFieldCount,
76
+ minFieldCount,
77
+ consistency: bestConsistency,
78
+ }
79
+ }
80
+
81
+ export function detectOutliers(
82
+ lines: string[],
83
+ delimiter: string,
84
+ minFieldCount: number,
85
+ ): OutlierInfo[] {
86
+ const outliers: OutlierInfo[] = []
87
+
88
+ for (let i = 0; i < lines.length; i++) {
89
+ const fieldCount = lines[i].split(delimiter).length
90
+ if (fieldCount < minFieldCount) {
91
+ outliers.push({
92
+ sample: lines[i],
93
+ index: i,
94
+ fieldCount,
95
+ action: 'include',
96
+ })
97
+ }
98
+ }
99
+
100
+ return outliers
101
+ }
102
+
103
+ const QC_KEYWORDS = new Set([
104
+ 'eqc', 'iqc', 'qc', 'blank', 'std', 'standard', 'test',
105
+ ])
106
+
107
+ export function classifyOutlierAction(
108
+ sample: string,
109
+ delimiter: string,
110
+ ): OutlierAction {
111
+ const segments = sample.split(delimiter)
112
+ return segments.some(seg => QC_KEYWORDS.has(seg.toLowerCase()))
113
+ ? 'qc'
114
+ : 'include'
115
+ }
116
+
117
+ // A column is useful for grouping when it has more than one distinct value
118
+ // AND it does not produce a unique value per row (which would create N
119
+ // singleton groups instead of meaningful aggregation).
120
+ export function isUsefulField(field: ColumnInfo, rowCount: number): boolean {
121
+ return field.cardinality > 1 && !(rowCount > 1 && field.cardinality === rowCount)
122
+ }
123
+
124
+ export function extractColumns(
125
+ samples: string[],
126
+ delimiter: string,
127
+ minFieldCount: number,
128
+ ): ColumnInfo[] {
129
+ if (samples.length === 0) return []
130
+
131
+ const suffixCount = minFieldCount - 1
132
+ const rows = samples.map(s => {
133
+ const parts = s.split(delimiter)
134
+ const splitAt = parts.length - suffixCount
135
+ return [
136
+ parts.slice(0, splitAt).join(delimiter),
137
+ ...parts.slice(splitAt),
138
+ ]
139
+ })
140
+
141
+ const columnCount = minFieldCount
142
+ const columns: ColumnInfo[] = []
143
+ for (let col = 0; col < columnCount; col++) {
144
+ const values = rows.map(row => row[col])
145
+ const unique = [...new Set(values)]
146
+ columns.push({
147
+ index: col,
148
+ name: col === 0 ? 'Condition' : `Field ${col + 1}`,
149
+ uniqueValues: unique,
150
+ cardinality: unique.length,
151
+ type: col === 0 ? 'prefix' : 'suffix',
152
+ })
153
+ }
154
+
155
+ return columns
156
+ }
157
+
158
+ export function parseCSVLine(line: string, delimiter: string = ','): string[] {
159
+ const result: string[] = []
160
+ let current = ''
161
+ let inQuotes = false
162
+
163
+ for (let i = 0; i < line.length; i++) {
164
+ const char = line[i]
165
+ if (char === '"') {
166
+ inQuotes = !inQuotes
167
+ } else if (char === delimiter && !inQuotes) {
168
+ result.push(current.trim())
169
+ current = ''
170
+ } else {
171
+ current += char
172
+ }
173
+ }
174
+ result.push(current.trim())
175
+
176
+ return result
177
+ }
178
+
179
+ export function parseCSV(text: string): ParsedCsvData {
180
+ const lines = text.trim().split('\n')
181
+ if (lines.length < 2) {
182
+ throw new Error('CSV must have at least a header and one data row')
183
+ }
184
+
185
+ // Auto-detect delimiter: tab takes precedence if present in header
186
+ const firstLine = lines[0]
187
+ const csvDelimiter = firstLine.includes('\t') ? '\t' : ','
188
+
189
+ const headers = parseCSVLine(firstLine, csvDelimiter)
190
+ const rows: Record<string, string>[] = []
191
+
192
+ for (let i = 1; i < lines.length; i++) {
193
+ const values = parseCSVLine(lines[i], csvDelimiter)
194
+ if (values.length !== headers.length) continue
195
+ const row: Record<string, string> = {}
196
+ headers.forEach((header, idx) => {
197
+ row[header] = values[idx]
198
+ })
199
+ rows.push(row)
200
+ }
201
+
202
+ // Auto-detect sample column
203
+ const sampleKeywords = ['sample', 'name', 'id', 'sample_name', 'samplename', 'file name', 'filename', 'file_name']
204
+ const sampleColumn =
205
+ headers.find(h => sampleKeywords.includes(h.toLowerCase())) ?? headers[0]
206
+
207
+ return { columns: headers, rows, sampleColumn, delimiter: csvDelimiter }
208
+ }
209
+
210
+ export function computeGroups(
211
+ allSamples: string[],
212
+ columns: ColumnInfo[],
213
+ enabledFields: Set<number>,
214
+ outlierActions: Map<number, OutlierAction>,
215
+ delimiter: string,
216
+ minFieldCount: number,
217
+ ): { groups: SampleGroup[]; metadata: MetadataRow[]; excludedSamples: string[] } {
218
+ const excludedSamples: string[] = []
219
+ const qcSamples: string[] = []
220
+ const conformingSamples: string[] = []
221
+
222
+ for (let i = 0; i < allSamples.length; i++) {
223
+ const action = outlierActions.get(i)
224
+ if (action === 'exclude') {
225
+ excludedSamples.push(allSamples[i])
226
+ } else if (action === 'qc') {
227
+ qcSamples.push(allSamples[i])
228
+ } else {
229
+ conformingSamples.push(allSamples[i])
230
+ }
231
+ }
232
+
233
+ // Build group map
234
+ const groupMap = new Map<string, string[]>()
235
+ const metadata: MetadataRow[] = []
236
+ const enabledIndices = [...enabledFields].sort((a, b) => a - b)
237
+
238
+ const suffixCount = minFieldCount - 1
239
+
240
+ for (const sample of conformingSamples) {
241
+ const parts = sample.split(delimiter)
242
+ const splitAt = Math.max(1, parts.length - suffixCount)
243
+ const row = [
244
+ parts.slice(0, splitAt).join(delimiter),
245
+ ...parts.slice(splitAt),
246
+ ]
247
+
248
+ // Build group key from enabled columns
249
+ const keyParts: string[] = []
250
+ for (const idx of enabledIndices) {
251
+ if (idx < row.length && idx < columns.length) {
252
+ keyParts.push(row[idx])
253
+ }
254
+ }
255
+ const groupKey = keyParts.join(' / ')
256
+
257
+ if (!groupMap.has(groupKey)) {
258
+ groupMap.set(groupKey, [])
259
+ }
260
+ groupMap.get(groupKey)!.push(sample)
261
+
262
+ // Build metadata row with ALL columns
263
+ const fields: Record<string, string> = {}
264
+ for (const col of columns) {
265
+ if (col.index < row.length) {
266
+ fields[col.name] = row[col.index]
267
+ }
268
+ }
269
+ metadata.push({ sampleName: sample, fields, group: groupKey })
270
+ }
271
+
272
+ // Convert to SampleGroup[]
273
+ const groups: SampleGroup[] = []
274
+ let colorIdx = 0
275
+ for (const [name, samples] of groupMap) {
276
+ groups.push({
277
+ name,
278
+ color: DEFAULT_COLORS[colorIdx % DEFAULT_COLORS.length],
279
+ samples,
280
+ })
281
+ colorIdx++
282
+ }
283
+
284
+ // QC group
285
+ if (qcSamples.length > 0) {
286
+ groups.push({
287
+ name: 'QC',
288
+ color: '#6B7280',
289
+ samples: qcSamples,
290
+ })
291
+ for (const sample of qcSamples) {
292
+ metadata.push({ sampleName: sample, fields: {}, group: 'QC' })
293
+ }
294
+ }
295
+
296
+ return { groups, metadata, excludedSamples }
297
+ }
298
+
299
+ /**
300
+ * Extract sample metadata from raw design_data into ParsedCsvData format.
301
+ *
302
+ * Looks for a `samples` array in the design data. For each sample, merges
303
+ * the `conditions` dict (the metadata table) with the `sample_name` to
304
+ * produce a flat tabular row. QC and blank samples are filtered out by
305
+ * their explicit `sample_type` field.
306
+ *
307
+ * Returns null if no samples with conditions are found.
308
+ */
309
+ export function extractSamplesFromDesignData(
310
+ rawData: Record<string, unknown>,
311
+ ): ParsedCsvData | null {
312
+ const samples = rawData.samples
313
+ if (!Array.isArray(samples) || samples.length === 0) return null
314
+
315
+ // Single pass: filter QC/blank and collect all condition keys
316
+ const allConditionKeys: string[] = []
317
+ const keySet = new Set<string>()
318
+ const filteredSamples: Record<string, unknown>[] = []
319
+
320
+ for (const sample of samples) {
321
+ const sampleType = String((sample as Record<string, unknown>).sample_type ?? 'sample').toLowerCase()
322
+ if (sampleType === 'qc' || sampleType === 'blank') continue
323
+
324
+ filteredSamples.push(sample as Record<string, unknown>)
325
+ const conditions = (sample as Record<string, unknown>).conditions as Record<string, string> | undefined
326
+ if (conditions && typeof conditions === 'object') {
327
+ for (const key of Object.keys(conditions)) {
328
+ if (!keySet.has(key)) {
329
+ keySet.add(key)
330
+ allConditionKeys.push(key)
331
+ }
332
+ }
333
+ }
334
+ }
335
+
336
+ if (filteredSamples.length === 0 || allConditionKeys.length === 0) return null
337
+
338
+ const columns = ['sample_name', ...allConditionKeys]
339
+ const rows: Record<string, string>[] = filteredSamples.map((sample) => {
340
+ const conditions = (sample.conditions as Record<string, string>) ?? {}
341
+ const row: Record<string, string> = {
342
+ sample_name: String(sample.sample_name ?? ''),
343
+ }
344
+ for (const key of allConditionKeys) {
345
+ row[key] = conditions[key] ?? ''
346
+ }
347
+ return row
348
+ })
349
+
350
+ return { columns, rows, sampleColumn: 'sample_name', delimiter: ',' }
351
+ }
352
+
353
+ export function computeGroupsFromCsv(
354
+ csvData: ParsedCsvData,
355
+ columns: ColumnInfo[],
356
+ enabledFields: Set<number>,
357
+ ): { groups: SampleGroup[]; metadata: MetadataRow[]; excludedSamples: string[] } {
358
+ const groupMap = new Map<string, string[]>()
359
+ const metadata: MetadataRow[] = []
360
+ const enabledCols = columns
361
+ .filter(c => enabledFields.has(c.index))
362
+ .sort((a, b) => a.index - b.index)
363
+
364
+ for (const row of csvData.rows) {
365
+ const sampleName = row[csvData.sampleColumn]
366
+
367
+ // Build group key from enabled CSV column values
368
+ // Use originalName for CSV row lookup (survives user renames), display name for group key
369
+ const keyParts = enabledCols.map(col => row[col.originalName ?? col.name])
370
+ const groupKey = keyParts.join(' / ')
371
+
372
+ if (!groupMap.has(groupKey)) {
373
+ groupMap.set(groupKey, [])
374
+ }
375
+ groupMap.get(groupKey)!.push(sampleName)
376
+
377
+ // Build metadata row with ALL columns — use display name as key, original for lookup
378
+ const fields: Record<string, string> = {}
379
+ for (const col of columns) {
380
+ fields[col.name] = row[col.originalName ?? col.name]
381
+ }
382
+ metadata.push({ sampleName, fields, group: groupKey })
383
+ }
384
+
385
+ // Convert to SampleGroup[]
386
+ const groups: SampleGroup[] = []
387
+ let colorIdx = 0
388
+ for (const [name, samples] of groupMap) {
389
+ groups.push({
390
+ name,
391
+ color: DEFAULT_COLORS[colorIdx % DEFAULT_COLORS.length],
392
+ samples,
393
+ })
394
+ colorIdx++
395
+ }
396
+
397
+ return { groups, metadata, excludedSamples: [] }
398
+ }
399
+
400
+ // --- Reactive composable ---
401
+
402
+ /** Parses sample names or CSV data to propose group assignments with outlier detection and preview. */
403
+ export function useAutoGroup() {
404
+ const inputMode = ref<InputMode>('paste')
405
+ const rawText = ref('')
406
+ const csvData = ref<ParsedCsvData | null>(null)
407
+ const delimiter = ref('_')
408
+ const dominantFieldCount = ref(1)
409
+ const minFieldCount = ref(1)
410
+ const outliers = ref<OutlierInfo[]>([])
411
+ const fields = ref<ColumnInfo[]>([])
412
+ const fieldNames = ref<Record<number, string>>({})
413
+ const enabledFields = ref(new Set<number>())
414
+
415
+ const isTabularMode = computed(() =>
416
+ (inputMode.value === 'csv' || inputMode.value === 'experiment') && csvData.value !== null,
417
+ )
418
+
419
+ const samples = computed(() => {
420
+ if (isTabularMode.value && csvData.value) {
421
+ return csvData.value.rows.map(r => r[csvData.value!.sampleColumn])
422
+ }
423
+ return rawText.value
424
+ .split('\n')
425
+ .map(l => l.trim())
426
+ .filter(l => l.length > 0)
427
+ })
428
+
429
+ const hasOutliers = computed(() => outliers.value.length > 0)
430
+
431
+ const conformingSamples = computed(() => {
432
+ const outlierIndices = new Set(outliers.value.map(o => o.index))
433
+ return samples.value.filter((_, i) => !outlierIndices.has(i))
434
+ })
435
+
436
+ const outlierActions = computed(() => {
437
+ const map = new Map<number, OutlierAction>()
438
+ for (const o of outliers.value) {
439
+ map.set(o.index, o.action)
440
+ }
441
+ return map
442
+ })
443
+
444
+ const effectiveColumns = computed(() => {
445
+ return fields.value.map(col => ({
446
+ ...col,
447
+ name: fieldNames.value[col.index] ?? col.name,
448
+ }))
449
+ })
450
+
451
+ const _computedResult = computed((): AutoGroupResult => {
452
+ if (effectiveColumns.value.length === 0 || enabledFields.value.size === 0) {
453
+ return { groups: [], metadata: [], excludedSamples: [] }
454
+ }
455
+
456
+ if (isTabularMode.value && csvData.value) {
457
+ return computeGroupsFromCsv(
458
+ csvData.value,
459
+ effectiveColumns.value,
460
+ enabledFields.value,
461
+ )
462
+ }
463
+
464
+ return computeGroups(
465
+ samples.value,
466
+ effectiveColumns.value,
467
+ enabledFields.value,
468
+ outlierActions.value,
469
+ delimiter.value,
470
+ minFieldCount.value,
471
+ )
472
+ })
473
+
474
+ const groups = computed(() => _computedResult.value.groups)
475
+ const metadata = computed(() => _computedResult.value.metadata)
476
+ const excludedSamples = computed(() => _computedResult.value.excludedSamples)
477
+
478
+ const allSingletons = computed(() =>
479
+ groups.value.length > 1 && groups.value.every(g => g.samples.length === 1),
480
+ )
481
+
482
+ const result = _computedResult
483
+
484
+ function parseInput() {
485
+ if (isTabularMode.value) {
486
+ parseCsvInput()
487
+ } else {
488
+ parsePasteInput()
489
+ }
490
+ }
491
+
492
+ function parsePasteInput() {
493
+ const lines = samples.value
494
+ if (lines.length === 0) return
495
+
496
+ const analysis = analyzeDelimiter(lines)
497
+ delimiter.value = analysis.delimiter
498
+ dominantFieldCount.value = analysis.dominantFieldCount
499
+
500
+ // Use dominantFieldCount as outlier threshold so QC/test samples with
501
+ // fewer fields than the majority are correctly flagged
502
+ outliers.value = detectOutliers(lines, analysis.delimiter, analysis.dominantFieldCount)
503
+
504
+ // Apply smart default actions: auto-classify QC/test samples
505
+ for (const outlier of outliers.value) {
506
+ outlier.action = classifyOutlierAction(outlier.sample, analysis.delimiter)
507
+ }
508
+
509
+ const conforming = lines.filter(
510
+ (_, i) => !outliers.value.some(o => o.index === i)
511
+ )
512
+
513
+ // Recompute minFieldCount from conforming samples only
514
+ const conformingFieldCounts = conforming.map(s => s.split(analysis.delimiter).length)
515
+ minFieldCount.value = conformingFieldCounts.length > 0
516
+ ? Math.min(...conformingFieldCounts)
517
+ : analysis.dominantFieldCount
518
+
519
+ fields.value = extractColumns(conforming, analysis.delimiter, minFieldCount.value)
520
+
521
+ fieldNames.value = {}
522
+ const rowCount = conforming.length
523
+ enabledFields.value = new Set(
524
+ fields.value.filter(f => isUsefulField(f, rowCount)).map(f => f.index),
525
+ )
526
+ }
527
+
528
+ function parseCsvInput() {
529
+ if (!csvData.value) return
530
+
531
+ const csv = csvData.value
532
+ const nonSampleCols = csv.columns.filter(c => c !== csv.sampleColumn)
533
+
534
+ fields.value = nonSampleCols.map((col, i) => {
535
+ const values = csv.rows.map(r => r[col])
536
+ const unique = [...new Set(values)]
537
+ return {
538
+ index: i,
539
+ name: col,
540
+ originalName: col,
541
+ uniqueValues: unique,
542
+ cardinality: unique.length,
543
+ }
544
+ })
545
+
546
+ // For CSV, no outliers
547
+ outliers.value = []
548
+ delimiter.value = csv.delimiter
549
+ dominantFieldCount.value = csv.columns.length
550
+
551
+ fieldNames.value = Object.fromEntries(fields.value.map(f => [f.index, f.name]))
552
+ const rowCount = csv.rows.length
553
+ enabledFields.value = new Set(
554
+ fields.value.filter(f => isUsefulField(f, rowCount)).map(f => f.index),
555
+ )
556
+ }
557
+
558
+ function setOutlierAction(index: number, action: OutlierAction) {
559
+ const outlier = outliers.value.find(o => o.index === index)
560
+ if (outlier) {
561
+ outlier.action = action
562
+ // Trigger reactivity
563
+ outliers.value = [...outliers.value]
564
+ }
565
+ }
566
+
567
+ function setAllOutlierActions(action: OutlierAction) {
568
+ for (const outlier of outliers.value) {
569
+ outlier.action = action
570
+ }
571
+ outliers.value = [...outliers.value]
572
+ }
573
+
574
+ function toggleField(index: number) {
575
+ const newSet = new Set(enabledFields.value)
576
+ if (newSet.has(index)) {
577
+ newSet.delete(index)
578
+ } else {
579
+ newSet.add(index)
580
+ }
581
+ enabledFields.value = newSet
582
+ }
583
+
584
+ function renameField(index: number, name: string) {
585
+ fieldNames.value = { ...fieldNames.value, [index]: name }
586
+ }
587
+
588
+ function loadExperimentData(rawData: Record<string, unknown>): boolean {
589
+ const parsed = extractSamplesFromDesignData(rawData)
590
+ if (!parsed) return false
591
+
592
+ inputMode.value = 'experiment'
593
+ csvData.value = parsed
594
+ parseCsvInput()
595
+ return true
596
+ }
597
+
598
+ function reset() {
599
+ rawText.value = ''
600
+ csvData.value = null
601
+ delimiter.value = '_'
602
+ dominantFieldCount.value = 1
603
+ minFieldCount.value = 1
604
+ outliers.value = []
605
+ fields.value = []
606
+ fieldNames.value = {}
607
+ enabledFields.value = new Set()
608
+ }
609
+
610
+ return {
611
+ // State
612
+ inputMode,
613
+ rawText,
614
+ csvData,
615
+ delimiter,
616
+ dominantFieldCount,
617
+ minFieldCount,
618
+ outliers,
619
+ fields,
620
+ fieldNames,
621
+ enabledFields,
622
+ // Computed
623
+ samples,
624
+ hasOutliers,
625
+ conformingSamples,
626
+ groups,
627
+ metadata,
628
+ excludedSamples,
629
+ allSingletons,
630
+ result,
631
+ effectiveColumns,
632
+ // Actions
633
+ parseInput,
634
+ loadExperimentData,
635
+ setOutlierAction,
636
+ setAllOutlierActions,
637
+ toggleField,
638
+ renameField,
639
+ reset,
640
+ }
641
+ }