@morscherlab/mint-sdk 1.5.0-beta.6 → 1.5.0-beta.8
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.
- package/dist/{AlertBox-CexONPhA.js → AlertBox-Cai0w7gF.js} +3 -3
- package/dist/{AlertBox-CexONPhA.js.map → AlertBox-Cai0w7gF.js.map} +1 -1
- package/dist/{AppSidebar-FxlF5_dI.js → AppSidebar-C1DT4nsZ.js} +351 -90
- package/dist/AppSidebar-C1DT4nsZ.js.map +1 -0
- package/dist/{ConfirmDialog-D9QXFpsy.js → ConfirmDialog-BjFsOpBe.js} +2 -2
- package/dist/{ConfirmDialog-D9QXFpsy.js.map → ConfirmDialog-BjFsOpBe.js.map} +1 -1
- package/dist/{ExperimentPopover-Bbd6RaoH.js → ExperimentPopover-DYy_s0kI.js} +25 -37
- package/dist/ExperimentPopover-DYy_s0kI.js.map +1 -0
- package/dist/{ExperimentSelectorModal-CUkKmgfr.js → ExperimentSelectorModal-Bfh5bdkv.js} +5 -5
- package/dist/{ExperimentSelectorModal-CUkKmgfr.js.map → ExperimentSelectorModal-Bfh5bdkv.js.map} +1 -1
- package/dist/{SegmentedControl-C-sckoJZ.js → SegmentedControl-B8KgyOjd.js} +2 -2
- package/dist/{SegmentedControl-C-sckoJZ.js.map → SegmentedControl-B8KgyOjd.js.map} +1 -1
- package/dist/{SettingsModal-BIC0dAYc.js → SettingsModal-DPNblTe9.js} +7 -7
- package/dist/{SettingsModal-BIC0dAYc.js.map → SettingsModal-DPNblTe9.js.map} +1 -1
- package/dist/__tests__/components/AppTopBarCompact.test.d.ts +1 -0
- package/dist/__tests__/components/AppTopBarCompactReset.test.d.ts +1 -0
- package/dist/__tests__/components/AutoGroupModal.experimentLoad.test.d.ts +1 -0
- package/dist/components/AppTopBar.compact.d.ts +5 -0
- package/dist/components/AppTopBar.vue.d.ts +17 -17
- package/dist/components/PluginWorkspaceView.vue.d.ts +4 -0
- package/dist/components/internal/AppTopBarPageSelectorInternal.vue.d.ts +2 -2
- package/dist/components/internal/LucideIconInternal.vue.d.ts +17 -0
- package/dist/composables/appLayoutSidebar.d.ts +9 -0
- package/dist/composables/useVersionedSave.d.ts +5 -2
- package/dist/{controlSchemaModel-C_XqpkgE.js → controlSchemaModel-BtFNZj8t.js} +2 -2
- package/dist/{controlSchemaModel-C_XqpkgE.js.map → controlSchemaModel-BtFNZj8t.js.map} +1 -1
- package/dist/{data-CfY7o8QC.js → data-CJztFns8.js} +31 -15
- package/dist/{data-CfY7o8QC.js.map → data-CJztFns8.js.map} +1 -1
- package/dist/data.js +8 -8
- package/dist/{experimentStatus-DCn2QKG7.js → experimentStatus-Deokk7Br.js} +2 -2
- package/dist/{experimentStatus-DCn2QKG7.js.map → experimentStatus-Deokk7Br.js.map} +1 -1
- package/dist/index.js +14 -14
- package/dist/{install-QF3Sh82O.js → install-9FkKs0iW.js} +14 -15
- package/dist/{install-QF3Sh82O.js.map → install-9FkKs0iW.js.map} +1 -1
- package/dist/install.js +1 -1
- package/dist/{lab-BcX5JBSY.js → lab-BYhb_cJI.js} +22 -14
- package/dist/lab-BYhb_cJI.js.map +1 -0
- package/dist/lab.js +4 -4
- package/dist/{records-BAymyHAn.js → records-GtiVMlTQ.js} +198 -62
- package/dist/records-GtiVMlTQ.js.map +1 -0
- package/dist/{runtime-DngeJtdq.js → runtime-DcMsvlaS.js} +29 -12
- package/dist/runtime-DcMsvlaS.js.map +1 -0
- package/dist/runtime.js +6 -6
- package/dist/{shell-CWZi97nW.js → shell-XZYkAZS_.js} +5 -5
- package/dist/{shell-CWZi97nW.js.map → shell-XZYkAZS_.js.map} +1 -1
- package/dist/shell.js +5 -5
- package/dist/styles.css +136 -12
- package/dist/types/components.d.ts +22 -2
- package/dist/types/index.d.ts +1 -1
- package/dist/{useDropdownState-uFIz6Yv8.js → useDropdownState-CcBzUYZ6.js} +14 -5
- package/dist/useDropdownState-CcBzUYZ6.js.map +1 -0
- package/dist/{useExperimentSamples-DEDAKa2E.js → useExperimentSamples-CpNXvpUn.js} +3 -3
- package/dist/{useExperimentSamples-DEDAKa2E.js.map → useExperimentSamples-CpNXvpUn.js.map} +1 -1
- package/dist/{useListReorder-W1bzK_bo.js → useListReorder-Bp4EAI2w.js} +3 -3
- package/dist/{useListReorder-W1bzK_bo.js.map → useListReorder-Bp4EAI2w.js.map} +1 -1
- package/dist/{useRackEditor-Bmtuo0aj.js → useRackEditor-i2bdPUFf.js} +8 -8
- package/dist/{useRackEditor-Bmtuo0aj.js.map → useRackEditor-i2bdPUFf.js.map} +1 -1
- package/dist/{useRealtime-jOetF2mM.js → useRealtime-BLQEjbuq.js} +3 -3
- package/dist/{useRealtime-jOetF2mM.js.map → useRealtime-BLQEjbuq.js.map} +1 -1
- package/dist/useRequest-CXyPw3eh.js +76 -0
- package/dist/useRequest-CXyPw3eh.js.map +1 -0
- package/dist/utils/deprecation.d.ts +2 -0
- package/dist/utils/lucideIcon.d.ts +4 -0
- package/package.json +1 -1
- package/src/__tests__/components/AppTopBar.test.ts +102 -20
- package/src/__tests__/components/AppTopBarCompact.test.ts +65 -0
- package/src/__tests__/components/AppTopBarCompactReset.test.ts +24 -0
- package/src/__tests__/components/AutoGroupModal.experimentLoad.test.ts +58 -0
- package/src/__tests__/components/BaseModal.test.ts +33 -2
- package/src/__tests__/components/ExperimentPopover.test.ts +32 -0
- package/src/__tests__/components/PluginWorkspaceView.test.ts +1 -1
- package/src/__tests__/composables/useJobCenter.test.ts +35 -0
- package/src/__tests__/composables/useVersionedSave.test.ts +71 -6
- package/src/__tests__/styles/menusShellRefresh.test.ts +5 -0
- package/src/components/AppLayout.story.vue +5 -5
- package/src/components/AppLayout.vue +13 -1
- package/src/components/AppTopBar.compact.ts +60 -0
- package/src/components/AppTopBar.story.vue +39 -14
- package/src/components/AppTopBar.vue +222 -89
- package/src/components/BaseModal.vue +16 -5
- package/src/components/ExperimentPopover.story.vue +2 -2
- package/src/components/ExperimentPopover.vue +6 -22
- package/src/components/GeneratedPluginView.vue +0 -1
- package/src/components/PluginWorkspaceView.vue +8 -0
- package/src/components/internal/AppTopBarPageSelectorInternal.vue +12 -0
- package/src/components/internal/AutoGroupModalInternal.vue +14 -3
- package/src/components/internal/LucideIconInternal.vue +23 -0
- package/src/composables/appLayoutSidebar.ts +11 -0
- package/src/composables/useJobCenter.ts +21 -1
- package/src/composables/usePluginWorkspace.ts +1 -1
- package/src/composables/useVersionedSave.ts +32 -7
- package/src/styles/components/app-layout.css +5 -0
- package/src/styles/components/app-top-bar.css +128 -8
- package/src/types/components.ts +23 -2
- package/src/types/index.ts +1 -0
- package/src/utils/deprecation.ts +8 -0
- package/src/utils/lucideIcon.ts +25 -0
- package/src/utils/lucideIconIndex.d.ts +2 -0
- package/dist/AppSidebar-FxlF5_dI.js.map +0 -1
- package/dist/ExperimentPopover-Bbd6RaoH.js.map +0 -1
- package/dist/lab-BcX5JBSY.js.map +0 -1
- package/dist/records-BAymyHAn.js.map +0 -1
- package/dist/runtime-DngeJtdq.js.map +0 -1
- package/dist/useDropdownState-uFIz6Yv8.js.map +0 -1
- package/dist/useRequest-D9-3BS-3.js +0 -215
- package/dist/useRequest-D9-3BS-3.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lab-BYhb_cJI.js","names":["$slots"],"sources":["../src/components/BaseTabs.vue","../src/components/BaseTabs.vue","../src/components/BasePill.vue","../src/components/BasePill.vue","../src/composables/controlSchemaDoseDesign.ts","../src/components/ProgressBar.vue","../src/components/ProgressBar.vue","../src/components/SmartGroupFieldRecipe.groups.ts","../src/components/SmartGroupFieldRecipe.vue","../src/components/SmartGroupFieldRecipe.vue","../src/components/SmartGroupManual.cohorts.ts","../src/components/SmartGroupManual.vue","../src/components/SmartGroupManual.vue","../src/components/internal/autoGroup/classKey.ts","../src/components/internal/autoGroup/vocab.json","../src/components/internal/autoGroup/classify.ts","../src/components/internal/autoGroup/tokenize.ts","../src/components/internal/autoGroup/columns.ts","../src/components/internal/autoGroup/cooccurrence.ts","../src/components/internal/autoGroup/colors.ts","../src/components/internal/autoGroup/compose.ts","../src/components/internal/autoGroup/fingerprint.ts","../src/components/internal/autoGroup/template.ts","../src/components/internal/autoGroup/tokenLength.ts","../src/components/internal/autoGroup/csv-shim.ts","../src/components/internal/useAutoGroup.ts","../src/components/internal/AutoGroupModalInternal.adapter.ts","../src/components/SampleSelector.colors.ts","../src/components/internal/AutoGroupModalInternal.vue","../src/components/internal/AutoGroupModalInternal.vue","../src/components/SampleSelectorSampleRow.vue","../src/components/SampleSelectorSampleRow.vue","../src/components/SampleSelector.tree.ts","../src/components/SampleSelectorGroupNode.vue","../src/components/SampleSelectorGroupNode.vue","../src/utils/sampleGroupPath.ts","../src/composables/useSampleGroups.ts","../src/components/SampleSelector.groups.ts","../src/components/SampleSelector.drag.ts","../src/components/SampleSelector.selection.ts","../src/components/SampleSelector.vue","../src/components/SampleSelector.vue","../src/components/SampleHierarchyTree.vue","../src/components/SampleHierarchyTree.vue","../src/composables/protocolTemplateCatalog.ts","../src/composables/useProtocolTemplates.ts","../src/components/ProtocolStep.presentation.ts","../src/components/ProtocolStepParameterField.vue","../src/components/ProtocolStepParameterField.vue","../src/components/ProtocolStepEditor.state.ts","../src/components/ProtocolStepEditor.vue","../src/components/ProtocolStepEditor.vue","../src/components/ScientificNumber.vue","../src/components/ScientificNumber.vue","../src/components/AdductText.vue","../src/components/AdductText.vue","../src/components/ChemicalFormula.vue","../src/components/ChemicalFormula.vue","../src/instrument.ts","../src/components/SequenceProgressBar.vue","../src/components/SequenceProgressBar.vue","../src/components/InstrumentStateBadge.vue","../src/components/InstrumentStateBadge.vue","../src/components/InstrumentStatusPanels.vue","../src/components/InstrumentStatusPanels.vue","../src/components/TimeRangeInput.vue","../src/components/TimeRangeInput.vue","../src/components/ResourceCard.vue","../src/components/ResourceCard.vue","../src/composables/useReagentSeries.ts","../src/lab.ts"],"sourcesContent":["<script setup lang=\"ts\">\n/** Renders an underline tab bar with mono count badges, icons, and disabled tab support. */\nimport { computed, type ComponentPublicInstance } from 'vue'\nimport type { OptionPrimitive, SelectOption, SelectOptionInput } from '../types'\nimport { normalizeOptionInput } from '../utils/options'\nimport { useRovingFocus } from '../composables/useRovingFocus'\n\ntype Tab = SelectOption<OptionPrimitive> & { id: string }\n\ninterface Props {\n modelValue: OptionPrimitive\n /** Tabs; `icon` takes SVG path data, `badge` a count shown after the label. */\n options: SelectOptionInput[]\n}\n\nconst props = defineProps<Props>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: OptionPrimitive]\n}>()\n\nconst activeTab = computed(() => String(props.modelValue))\nconst normalizedTabs = computed<Tab[]>(() =>\n props.options.map((input) => {\n const option = normalizeOptionInput<OptionPrimitive, SelectOption<OptionPrimitive>>(input)\n return { ...option, id: String(option.value) }\n }),\n)\n\nfunction isSvgIcon(icon: Tab['icon']): icon is string | string[] {\n if (!icon) return false\n return Array.isArray(icon) || icon.trim().startsWith('M') || icon.trim().startsWith('m')\n}\n\nfunction selectTab(tabId: string) {\n const tab = normalizedTabs.value.find(t => t.id === tabId)\n if (tab && !tab.disabled) {\n emit('update:modelValue', tab.value)\n }\n}\n\nconst tabButtonRefs = new Map<string, HTMLElement>()\n\nfunction setTabButtonRef(id: string, el: Element | ComponentPublicInstance | null) {\n if (el instanceof HTMLElement) {\n tabButtonRefs.set(id, el)\n } else {\n tabButtonRefs.delete(id)\n }\n}\n\n// Left/Right only — BaseTabs renders a single horizontal strip with no vertical\n// variant, so there is no Up/Down axis to support.\nconst { handleKeydown } = useRovingFocus({\n ids: () => normalizedTabs.value.filter(tab => !tab.disabled).map(tab => tab.id),\n activeId: () => activeTab.value,\n activate: selectTab,\n getElement: (id) => tabButtonRefs.get(id),\n})\n</script>\n\n<template>\n <div class=\"mint-tabs mint-tabs--underline\" role=\"tablist\">\n <button\n v-for=\"tab in normalizedTabs\"\n :id=\"`mint-tab-${tab.id}`\"\n :key=\"tab.id\"\n :ref=\"(el) => setTabButtonRef(tab.id, el)\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"activeTab === tab.id\"\n :aria-disabled=\"tab.disabled\"\n :aria-controls=\"`mint-tabpanel-${tab.id}`\"\n :tabindex=\"activeTab === tab.id ? 0 : -1\"\n :class=\"[\n 'mint-tab',\n activeTab === tab.id ? 'mint-tab--active' : '',\n tab.disabled ? 'mint-tab--disabled' : '',\n ]\"\n @click=\"selectTab(tab.id)\"\n @keydown=\"handleKeydown\"\n >\n <span class=\"mint-tab__content\">\n <svg\n v-if=\"isSvgIcon(tab.icon)\"\n class=\"mint-tab__icon mint-tab__icon--svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <template v-if=\"Array.isArray(tab.icon)\">\n <path v-for=\"(d, index) in tab.icon\" :key=\"index\" :d=\"d\" />\n </template>\n <path v-else :d=\"tab.icon\" />\n </svg>\n <span v-else-if=\"tab.icon\" class=\"mint-tab__icon\">{{ tab.icon }}</span>\n {{ tab.label }}\n <span v-if=\"tab.badge !== undefined\" class=\"mint-tab__badge\">\n {{ tab.badge }}\n </span>\n </span>\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Renders an underline tab bar with mono count badges, icons, and disabled tab support. */\nimport { computed, type ComponentPublicInstance } from 'vue'\nimport type { OptionPrimitive, SelectOption, SelectOptionInput } from '../types'\nimport { normalizeOptionInput } from '../utils/options'\nimport { useRovingFocus } from '../composables/useRovingFocus'\n\ntype Tab = SelectOption<OptionPrimitive> & { id: string }\n\ninterface Props {\n modelValue: OptionPrimitive\n /** Tabs; `icon` takes SVG path data, `badge` a count shown after the label. */\n options: SelectOptionInput[]\n}\n\nconst props = defineProps<Props>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: OptionPrimitive]\n}>()\n\nconst activeTab = computed(() => String(props.modelValue))\nconst normalizedTabs = computed<Tab[]>(() =>\n props.options.map((input) => {\n const option = normalizeOptionInput<OptionPrimitive, SelectOption<OptionPrimitive>>(input)\n return { ...option, id: String(option.value) }\n }),\n)\n\nfunction isSvgIcon(icon: Tab['icon']): icon is string | string[] {\n if (!icon) return false\n return Array.isArray(icon) || icon.trim().startsWith('M') || icon.trim().startsWith('m')\n}\n\nfunction selectTab(tabId: string) {\n const tab = normalizedTabs.value.find(t => t.id === tabId)\n if (tab && !tab.disabled) {\n emit('update:modelValue', tab.value)\n }\n}\n\nconst tabButtonRefs = new Map<string, HTMLElement>()\n\nfunction setTabButtonRef(id: string, el: Element | ComponentPublicInstance | null) {\n if (el instanceof HTMLElement) {\n tabButtonRefs.set(id, el)\n } else {\n tabButtonRefs.delete(id)\n }\n}\n\n// Left/Right only — BaseTabs renders a single horizontal strip with no vertical\n// variant, so there is no Up/Down axis to support.\nconst { handleKeydown } = useRovingFocus({\n ids: () => normalizedTabs.value.filter(tab => !tab.disabled).map(tab => tab.id),\n activeId: () => activeTab.value,\n activate: selectTab,\n getElement: (id) => tabButtonRefs.get(id),\n})\n</script>\n\n<template>\n <div class=\"mint-tabs mint-tabs--underline\" role=\"tablist\">\n <button\n v-for=\"tab in normalizedTabs\"\n :id=\"`mint-tab-${tab.id}`\"\n :key=\"tab.id\"\n :ref=\"(el) => setTabButtonRef(tab.id, el)\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"activeTab === tab.id\"\n :aria-disabled=\"tab.disabled\"\n :aria-controls=\"`mint-tabpanel-${tab.id}`\"\n :tabindex=\"activeTab === tab.id ? 0 : -1\"\n :class=\"[\n 'mint-tab',\n activeTab === tab.id ? 'mint-tab--active' : '',\n tab.disabled ? 'mint-tab--disabled' : '',\n ]\"\n @click=\"selectTab(tab.id)\"\n @keydown=\"handleKeydown\"\n >\n <span class=\"mint-tab__content\">\n <svg\n v-if=\"isSvgIcon(tab.icon)\"\n class=\"mint-tab__icon mint-tab__icon--svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n aria-hidden=\"true\"\n >\n <template v-if=\"Array.isArray(tab.icon)\">\n <path v-for=\"(d, index) in tab.icon\" :key=\"index\" :d=\"d\" />\n </template>\n <path v-else :d=\"tab.icon\" />\n </svg>\n <span v-else-if=\"tab.icon\" class=\"mint-tab__icon\">{{ tab.icon }}</span>\n {{ tab.label }}\n <span v-if=\"tab.badge !== undefined\" class=\"mint-tab__badge\">\n {{ tab.badge }}\n </span>\n </span>\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Compact label for tags, status indicators, and badges; supports a status dot, removable and icon slots. */\nimport { computed } from 'vue'\nimport type { PillVariant, PillSize, Tone } from '../types'\n\n/**\n * BasePill - Compact label component for tags, status indicators, and badges.\n *\n * @example\n * ```vue\n * <BasePill tone=\"success\">Active</BasePill>\n * <BasePill tone=\"warning\" removable @remove=\"handleRemove\">Tag</BasePill>\n * <BasePill tone=\"success\" dot pulse>Running</BasePill>\n * <BasePill :icon=\"true\">\n * <template #icon><CheckIcon /></template>\n * Verified\n * </BasePill>\n * ```\n */\ninterface Props {\n /** Semantic color */\n tone?: Tone\n /** Tinted fill (`soft`) or a 1px border in the tone color (`outline`) */\n variant?: PillVariant\n /** Custom accent color (any CSS color); replaces the tone's accent for the dot, tint and border */\n color?: string\n /** Size of the pill */\n size?: PillSize\n /** Show remove button */\n removable?: boolean\n /** Disable interaction */\n disabled?: boolean\n /** Show icon slot */\n icon?: boolean\n /** Leading status dot in the pill's tone color */\n dot?: boolean\n /** Animate the status dot (live status) */\n pulse?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n tone: 'neutral',\n variant: 'soft',\n color: undefined,\n size: 'md',\n removable: false,\n disabled: false,\n icon: false,\n dot: false,\n pulse: false,\n})\n\nconst accentStyle = computed(() => (props.color ? { '--mint-pill-tone': props.color } : undefined))\n\n/**\n * @event remove - Emitted when the remove button is clicked\n */\nconst emit = defineEmits<{\n remove: []\n}>()\n\nfunction handleRemove(event: MouseEvent) {\n event.stopPropagation()\n if (!props.disabled) {\n emit('remove')\n }\n}\n</script>\n\n<template>\n <span\n :class=\"[\n 'mint-pill',\n `mint-pill--${tone}`,\n `mint-pill--${variant}`,\n `mint-pill--${size}`,\n { 'mint-pill--disabled': disabled, 'mint-pill--with-icon': icon, 'mint-pill--accent': color },\n ]\"\n :style=\"accentStyle\"\n >\n <span v-if=\"dot\" :class=\"['mint-pill__dot', { 'mint-pill__dot--pulse': pulse }]\" aria-hidden=\"true\" />\n <span v-if=\"icon\" class=\"mint-pill__icon\">\n <slot name=\"icon\" />\n </span>\n <span class=\"mint-pill__label\">\n <slot />\n </span>\n <button\n v-if=\"removable && !disabled\"\n type=\"button\"\n class=\"mint-pill__remove\"\n aria-label=\"Remove\"\n @click=\"handleRemove\"\n >\n <svg class=\"mint-pill__remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </span>\n</template>\n","<script setup lang=\"ts\">\n/** Compact label for tags, status indicators, and badges; supports a status dot, removable and icon slots. */\nimport { computed } from 'vue'\nimport type { PillVariant, PillSize, Tone } from '../types'\n\n/**\n * BasePill - Compact label component for tags, status indicators, and badges.\n *\n * @example\n * ```vue\n * <BasePill tone=\"success\">Active</BasePill>\n * <BasePill tone=\"warning\" removable @remove=\"handleRemove\">Tag</BasePill>\n * <BasePill tone=\"success\" dot pulse>Running</BasePill>\n * <BasePill :icon=\"true\">\n * <template #icon><CheckIcon /></template>\n * Verified\n * </BasePill>\n * ```\n */\ninterface Props {\n /** Semantic color */\n tone?: Tone\n /** Tinted fill (`soft`) or a 1px border in the tone color (`outline`) */\n variant?: PillVariant\n /** Custom accent color (any CSS color); replaces the tone's accent for the dot, tint and border */\n color?: string\n /** Size of the pill */\n size?: PillSize\n /** Show remove button */\n removable?: boolean\n /** Disable interaction */\n disabled?: boolean\n /** Show icon slot */\n icon?: boolean\n /** Leading status dot in the pill's tone color */\n dot?: boolean\n /** Animate the status dot (live status) */\n pulse?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n tone: 'neutral',\n variant: 'soft',\n color: undefined,\n size: 'md',\n removable: false,\n disabled: false,\n icon: false,\n dot: false,\n pulse: false,\n})\n\nconst accentStyle = computed(() => (props.color ? { '--mint-pill-tone': props.color } : undefined))\n\n/**\n * @event remove - Emitted when the remove button is clicked\n */\nconst emit = defineEmits<{\n remove: []\n}>()\n\nfunction handleRemove(event: MouseEvent) {\n event.stopPropagation()\n if (!props.disabled) {\n emit('remove')\n }\n}\n</script>\n\n<template>\n <span\n :class=\"[\n 'mint-pill',\n `mint-pill--${tone}`,\n `mint-pill--${variant}`,\n `mint-pill--${size}`,\n { 'mint-pill--disabled': disabled, 'mint-pill--with-icon': icon, 'mint-pill--accent': color },\n ]\"\n :style=\"accentStyle\"\n >\n <span v-if=\"dot\" :class=\"['mint-pill__dot', { 'mint-pill__dot--pulse': pulse }]\" aria-hidden=\"true\" />\n <span v-if=\"icon\" class=\"mint-pill__icon\">\n <slot name=\"icon\" />\n </span>\n <span class=\"mint-pill__label\">\n <slot />\n </span>\n <button\n v-if=\"removable && !disabled\"\n type=\"button\"\n class=\"mint-pill__remove\"\n aria-label=\"Remove\"\n @click=\"handleRemove\"\n >\n <svg class=\"mint-pill__remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </span>\n</template>\n","import type {\n ControlComponentBindingsConfig,\n ControlComponentPropSource,\n ControlComponentPropsByIdMap,\n ControlComponentPropsMap,\n ControlModelBinding,\n ControlSchema,\n DoseCalculatorControlPropsOptions,\n DoseDesignControlModelOptions,\n WellPlateControlPropsOptions,\n WellPlateDoseControlPropsOptions,\n} from './controlSchemaTypes'\nimport type { WellConcentration } from '../utils/dose'\nimport {\n defineControlModel,\n} from './controlSchemaModel'\nimport {\n recordValue,\n} from './controlSchemaUtils'\n\n/** Return a default WellPlate prop mapping for generated control workspaces. */\nexport function defineWellPlateControlProps<TValues extends Record<string, unknown> = Record<string, unknown>>(\n options: WellPlateControlPropsOptions<TValues> = {},\n): ControlComponentPropsMap<TValues> {\n const selectedWells = options.selectedWells ?? sourceKey<TValues>('selectedWells')\n const onUpdateModelValue = options.onUpdateModelValue === false\n ? undefined\n : options.onUpdateModelValue ?? updateControlValueSource(selectedWells)\n\n const format = options.format ?? sourceKey<TValues>('plateFormat')\n const showLegend = options.showLegend\n\n return compactComponentPropsMap<TValues>({\n modelValue: selectedWells,\n 'onUpdate:modelValue': onUpdateModelValue,\n spec: (values: TValues) => ({\n format: readSource(values, format),\n showLegend: showLegend === undefined ? undefined : readSource(values, showLegend),\n }),\n wells: options.wells ?? sourceKey<TValues>('wells'),\n disabled: options.disabled ?? sourceKey<TValues>('disabled'),\n readonly: options.readonly ?? sourceKey<TValues>('readonly'),\n })\n}\n\n/** Return a default DoseCalculator prop mapping for generated control workspaces. */\nexport function defineDoseCalculatorControlProps<TValues extends Record<string, unknown> = Record<string, unknown>>(\n options: DoseCalculatorControlPropsOptions<TValues> = {},\n): ControlComponentPropsMap<TValues> {\n const targetWells = options.targetWells ?? sourceKey<TValues>('selectedWells')\n const wells = options.wells ?? sourceKey<TValues>('wells')\n const onApplyToWells = options.onApplyToWells === false\n ? undefined\n : options.onApplyToWells ?? defaultDoseApplyHandler(wells, targetWells)\n\n return compactComponentPropsMap<TValues>({\n mode: options.mode ?? sourceKey<TValues>('doseMode'),\n targetWells,\n disabled: options.disabled ?? sourceKey<TValues>('disabled'),\n molecularWeight: options.molecularWeight,\n onApplyToWells,\n })\n}\n\n/** Return named WellPlate + DoseCalculator prop mappings for one dose-design control model. */\nexport function defineWellPlateDoseControlProps<TValues extends Record<string, unknown> = Record<string, unknown>>(\n options: WellPlateDoseControlPropsOptions<TValues> = {},\n): ControlComponentPropsByIdMap<TValues> {\n return {\n [options.plateId ?? 'plate']: defineWellPlateControlProps(options.plate),\n [options.doseId ?? 'dose']: defineDoseCalculatorControlProps(options.dose),\n }\n}\n\n/** Return named WellPlate + DoseCalculator component bindings for one dose-design control model. */\nexport function defineWellPlateDoseComponentBindings<TValues extends Record<string, unknown> = Record<string, unknown>>(\n options: WellPlateDoseControlPropsOptions<TValues> = {},\n): ControlComponentBindingsConfig<TValues> {\n return [\n {\n id: options.plateId ?? 'plate',\n component: 'WellPlate',\n props: defineWellPlateControlProps(options.plate),\n },\n {\n id: options.doseId ?? 'dose',\n component: 'DoseCalculator',\n props: defineDoseCalculatorControlProps(options.dose),\n },\n ]\n}\n\n/**\n * Return a complete PluginWorkspaceView sidebar model for WellPlate + DoseCalculator dose design.\n *\n * @example Recommended\n * ```ts\n * const workspaceModel = defineDoseDesignControlModel({ selectedWells: ['A1', 'A2'] })\n * ```\n */\nexport function defineDoseDesignControlModel(\n options: DoseDesignControlModelOptions = {},\n): ControlModelBinding {\n const viewId = options.viewId ?? 'design'\n const sectionId = options.sectionId ?? 'dose'\n const doseControls: ControlSchema = {\n selectedWells: {\n type: 'tags',\n label: 'Selected wells',\n default: [...(options.selectedWells ?? ['A1', 'A2'])],\n },\n plateFormat: {\n label: 'Plate format',\n default: options.plateFormat ?? 96,\n options: options.plateFormats ?? [96, 384],\n },\n doseMode: {\n label: 'Dose mode',\n default: options.doseMode ?? 'serial',\n options: options.doseModes ?? ['serial', 'dilution', 'conversion'],\n },\n disabled: {\n label: 'Disable tools',\n default: options.disabled ?? false,\n },\n }\n\n if (options.includeMolecularWeight) {\n doseControls.molecularWeight = {\n type: 'number',\n label: 'Molecular weight',\n default: options.molecularWeight ?? 300,\n min: 0,\n }\n }\n\n const componentProps: WellPlateDoseControlPropsOptions = {\n ...options.componentProps,\n dose: {\n ...(options.componentProps?.dose ?? {}),\n ...(options.includeMolecularWeight && options.componentProps?.dose?.molecularWeight === undefined\n ? { molecularWeight: 'molecularWeight' }\n : {}),\n },\n }\n\n return defineControlModel({\n componentBindings: defineWellPlateDoseComponentBindings(componentProps),\n componentPropsById: defineWellPlateDoseControlProps(componentProps),\n views: {\n [viewId]: {\n label: options.viewLabel ?? 'Design',\n sections: {\n [sectionId]: {\n label: options.sectionLabel ?? 'Dose design',\n description: options.sectionDescription ?? 'Well selection and dose calculator controls',\n controls: doseControls,\n },\n },\n },\n },\n })\n}\n\nfunction compactComponentPropsMap<TValues extends Record<string, unknown>>(\n mapping: Record<string, ControlComponentPropSource<TValues> | undefined>,\n): ControlComponentPropsMap<TValues> {\n return Object.fromEntries(\n Object.entries(mapping).filter((entry): entry is [string, ControlComponentPropSource<TValues>] =>\n entry[1] !== undefined\n ),\n )\n}\n\nfunction readSource<TValues extends Record<string, unknown>>(\n values: TValues,\n source: ControlComponentPropSource<TValues>,\n): unknown {\n return typeof source === 'function' ? source(values) : values[source]\n}\n\nfunction sourceKey<TValues extends Record<string, unknown>>(key: string): keyof TValues & string {\n return key as keyof TValues & string\n}\n\nfunction updateControlValueSource<TValues extends Record<string, unknown>>(\n source: ControlComponentPropSource<TValues>,\n): ((values: TValues) => (value: string[]) => void) | undefined {\n if (typeof source !== 'string') return undefined\n return values => (value: string[]) => {\n const writableValues = values as Record<string, unknown>\n writableValues[source] = value\n }\n}\n\nfunction defaultDoseApplyHandler<TValues extends Record<string, unknown>>(\n wells: ControlComponentPropSource<TValues>,\n targetWells: ControlComponentPropSource<TValues>,\n): ((values: TValues) => (results: WellConcentration[]) => void) | undefined {\n if (wells === targetWells) return undefined\n return updateWellsFromDoseResults(wells)\n}\n\nfunction updateWellsFromDoseResults<TValues extends Record<string, unknown>>(\n source: ControlComponentPropSource<TValues>,\n): ((values: TValues) => (results: WellConcentration[]) => void) | undefined {\n if (typeof source !== 'string') return undefined\n return values => (results: WellConcentration[]) => {\n const writableValues = values as Record<string, unknown>\n const currentWells = recordValue(writableValues[source])\n const nextWells: Record<string, Record<string, unknown>> = {}\n for (const [wellId, well] of Object.entries(currentWells)) {\n nextWells[wellId] = recordValue(well)\n }\n\n for (const result of results) {\n const existing = recordValue(nextWells[result.wellId])\n const metadata = recordValue(existing.metadata)\n nextWells[result.wellId] = {\n ...existing,\n state: 'filled',\n value: result.concentration.value,\n metadata: {\n ...metadata,\n concentration: result.concentration,\n ...(result.volume === undefined ? {} : { volume: result.volume }),\n },\n }\n }\n\n writableValues[source] = nextWells\n }\n}\n","<script setup lang=\"ts\">\n/** Displays progress as a filled rail, with an indeterminate mode. */\nimport { computed } from 'vue'\nimport type { Tone } from '../types'\n\ninterface Props {\n value?: number\n tone?: Tone\n size?: 'sm' | 'md' | 'lg'\n label?: string\n showValue?: boolean\n indeterminate?: boolean\n ariaLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n value: 0,\n tone: 'primary',\n size: 'md',\n label: undefined,\n showValue: false,\n indeterminate: false,\n})\n\nconst clampedValue = computed(() => Math.min(100, Math.max(0, props.value)))\n\nconst barStyle = computed(() => {\n if (props.indeterminate) return undefined\n return { width: `${clampedValue.value}%` }\n})\n</script>\n\n<template>\n <div class=\"mint-progress\">\n <div\n v-if=\"label || showValue\"\n class=\"mint-progress__header\"\n >\n <span v-if=\"label\" class=\"mint-progress__label\">{{ label }}</span>\n <span v-if=\"showValue && !indeterminate\" class=\"mint-progress__value\">{{ clampedValue }}%</span>\n </div>\n\n <div\n :class=\"[\n 'mint-progress__track',\n `mint-progress__track--${size}`,\n ]\"\n role=\"progressbar\"\n :aria-valuenow=\"indeterminate ? undefined : clampedValue\"\n :aria-valuemin=\"indeterminate ? undefined : 0\"\n :aria-valuemax=\"indeterminate ? undefined : 100\"\n :aria-label=\"ariaLabel ?? label\"\n >\n <div\n :class=\"[\n 'mint-progress__bar',\n `mint-progress__bar--${tone}`,\n indeterminate ? 'mint-progress__bar--indeterminate' : '',\n ]\"\n :style=\"barStyle\"\n />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Displays progress as a filled rail, with an indeterminate mode. */\nimport { computed } from 'vue'\nimport type { Tone } from '../types'\n\ninterface Props {\n value?: number\n tone?: Tone\n size?: 'sm' | 'md' | 'lg'\n label?: string\n showValue?: boolean\n indeterminate?: boolean\n ariaLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n value: 0,\n tone: 'primary',\n size: 'md',\n label: undefined,\n showValue: false,\n indeterminate: false,\n})\n\nconst clampedValue = computed(() => Math.min(100, Math.max(0, props.value)))\n\nconst barStyle = computed(() => {\n if (props.indeterminate) return undefined\n return { width: `${clampedValue.value}%` }\n})\n</script>\n\n<template>\n <div class=\"mint-progress\">\n <div\n v-if=\"label || showValue\"\n class=\"mint-progress__header\"\n >\n <span v-if=\"label\" class=\"mint-progress__label\">{{ label }}</span>\n <span v-if=\"showValue && !indeterminate\" class=\"mint-progress__value\">{{ clampedValue }}%</span>\n </div>\n\n <div\n :class=\"[\n 'mint-progress__track',\n `mint-progress__track--${size}`,\n ]\"\n role=\"progressbar\"\n :aria-valuenow=\"indeterminate ? undefined : clampedValue\"\n :aria-valuemin=\"indeterminate ? undefined : 0\"\n :aria-valuemax=\"indeterminate ? undefined : 100\"\n :aria-label=\"ariaLabel ?? label\"\n >\n <div\n :class=\"[\n 'mint-progress__bar',\n `mint-progress__bar--${tone}`,\n indeterminate ? 'mint-progress__bar--indeterminate' : '',\n ]\"\n :style=\"barStyle\"\n />\n </div>\n </div>\n</template>\n","/**\n * Pure grouping logic for SmartGroupFieldRecipe (the \"auto\" / Field Recipe mode).\n *\n * Mirrors the design prototype's live grouping engine: groups are formed by\n * joining the values of whichever fields are switched on, in field order.\n * Kept framework-free so it is unit-testable in isolation from the component.\n */\n\n/** A field/factor the user can group by. */\nexport interface SmartGroupField {\n /** Column key as it appears in each sample record. */\n key: string\n /** Display role, e.g. `Factor` or `Replicate`. */\n role: string\n /** Comma-separated preview of the field's distinct values. */\n vals: string\n /** Whether this field is a replicate dimension (grouping by it shatters groups). */\n rep: boolean\n}\n\n/** A single sample as a flat key → value record. */\nexport type SmartGroupSampleRecord = Record<string, string>\n\n/** A resolved group in the live preview rail. */\nexport interface FieldRecipeGroup {\n name: string\n count: number\n color: string\n}\n\n/** Name of the QC pool group appended by `appendQcGroup`. */\nexport const QC_POOL_NAME = 'QC_Pool'\n\n/** How QC samples are routed relative to the biological groups. */\nexport type QcRoute = 'Overlay only' | 'Exclude' | 'Mix into groups'\n\n/**\n * Kind of inline summary callout to render. `warn`: a replicate field is on.\n * `singletons`: no replicate field is on, but half or more of the groups have one sample.\n */\nexport type FieldRecipeSummaryKind = 'mute' | 'warn' | 'singletons' | 'info'\n\nexport interface FieldRecipeSummary {\n kind: FieldRecipeSummaryKind\n /** Number of base (non-QC) groups the current toggles produce. */\n groupCount: number\n}\n\n/**\n * Build the ordered group list from whichever fields are switched on.\n * Returns an empty list when no field is active. `layerOrder` (field indices\n * in layer order) overrides the default field-order joining of the group name.\n */\nexport function computeFieldGroups(\n samples: SmartGroupSampleRecord[],\n fields: SmartGroupField[],\n on: boolean[],\n palette: string[],\n layerOrder?: readonly number[],\n): FieldRecipeGroup[] {\n const keys = layerOrder\n ? layerOrder.filter(i => on[i]).map(i => fields[i].key)\n : fields.filter((_, i) => on[i]).map(field => field.key)\n if (keys.length === 0) return []\n\n const order: string[] = []\n const counts = new Map<string, number>()\n for (const sample of samples) {\n const name = keys.map(key => sample[key]).join('_')\n if (!counts.has(name)) {\n counts.set(name, 0)\n order.push(name)\n }\n counts.set(name, (counts.get(name) ?? 0) + 1)\n }\n\n return order.map((name, index) => ({\n name,\n count: counts.get(name) ?? 0,\n color: palette[index % palette.length],\n }))\n}\n\n/**\n * Append the QC pool group when QC is mixed into the biological groups.\n * `Overlay only` and `Exclude` leave the group list untouched.\n */\nexport function appendQcGroup(\n groups: FieldRecipeGroup[],\n route: QcRoute,\n qcColor: string,\n qcCount = 3,\n): FieldRecipeGroup[] {\n if (route === 'Mix into groups') {\n return [...groups, { name: QC_POOL_NAME, count: qcCount, color: qcColor }]\n }\n return groups\n}\n\n/** Total samples the grouping applies to: QC is dropped only when excluded. */\nexport function sampleTotalFor(route: QcRoute, realCount = 9, qcCount = 3): number {\n return route === 'Exclude' ? realCount : realCount + qcCount\n}\n\n/**\n * True when half or more of the groups have one sample: the design has no\n * replicate in the names, or a field that is on is not part of the condition.\n */\nexport function mostlySingletons(groups: FieldRecipeGroup[]): boolean {\n return groups.length > 1 && groups.filter(group => group.count === 1).length * 2 >= groups.length\n}\n\n/** Decide which inline summary callout the Field Recipe should show. */\nexport function fieldRecipeSummary(input: {\n activeFactors: string[]\n repOn: boolean\n baseGroups: FieldRecipeGroup[]\n}): FieldRecipeSummary {\n const groupCount = input.baseGroups.length\n if (input.activeFactors.length === 0) return { kind: 'mute', groupCount }\n if (input.repOn) return { kind: 'warn', groupCount }\n if (mostlySingletons(input.baseGroups)) return { kind: 'singletons', groupCount }\n return { kind: 'info', groupCount }\n}\n","<script lang=\"ts\">\n/**\n * SmartGroupFieldRecipe — the \"auto\" mode of the Smart Group modal.\n *\n * MINT parses the structure of sample names into candidate fields; the user\n * switches on the factors that define their groups. A live preview rail\n * reflects the resulting groups, an inline summary explains what will happen\n * (including the replicate \"singleton trap\"), and a QC routing control decides\n * how quality-control samples are handled. Grouping maths live in the tested\n * `./SmartGroupFieldRecipe.groups` module.\n *\n * The reference-fixture defaults live in this module-scope `<script>` block\n * because `defineProps` default factories are hoisted and cannot reference\n * bindings declared inside `<script setup>`.\n */\nimport type { SmartGroupField, SmartGroupSampleRecord } from './SmartGroupFieldRecipe.groups'\nimport { SAMPLE_GROUP_TOKENS } from '../utils/color'\n\nconst FR_BASE = [\n { Condition: 'Control', Dose: 'Vehicle' },\n { Condition: 'Treatment', Dose: 'Low' },\n { Condition: 'Treatment', Dose: 'High' },\n]\nconst DEFAULT_SAMPLES: SmartGroupSampleRecord[] = FR_BASE.flatMap(base =>\n [1, 2, 3].map(rep => ({ ...base, Replicate: `Rep${rep}` })),\n)\nconst DEFAULT_FIELDS: SmartGroupField[] = [\n { key: 'Condition', role: 'Factor', vals: 'Control, Treatment', rep: false },\n { key: 'Dose', role: 'Factor', vals: 'Vehicle, Low, High', rep: false },\n { key: 'Replicate', role: 'Replicate', vals: 'Rep1, Rep2, Rep3', rep: true },\n]\nconst DEFAULT_PALETTE = [...SAMPLE_GROUP_TOKENS]\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, h, nextTick, ref, type FunctionalComponent } from 'vue'\nimport {\n appendQcGroup,\n computeFieldGroups,\n fieldRecipeSummary,\n QC_POOL_NAME,\n sampleTotalFor,\n type QcRoute,\n} from './SmartGroupFieldRecipe.groups'\nimport { sampleTypeMarker } from '../utils/sampleTypeMarker'\nimport { useListReorder } from '../composables/useListReorder'\nimport ActionMenu from './ActionMenu.vue'\nimport BaseButton from './BaseButton.vue'\nimport BaseCheckbox from './BaseCheckbox.vue'\nimport LoadingSpinner from './LoadingSpinner.vue'\nimport SegmentedControl from './SegmentedControl.vue'\nimport type { ActionMenuItem } from '../types/components'\nimport type { FieldRecipeApplyPayload, FieldRecipeQcChip, SmartGroupFieldRecipeProps } from './SmartGroup.types'\n\nconst props = withDefaults(defineProps<SmartGroupFieldRecipeProps>(), {\n samples: () => DEFAULT_SAMPLES,\n fields: () => DEFAULT_FIELDS,\n experimentLabel: 'EXP-014',\n headerCount: 12,\n initialRoute: 'Overlay only',\n palette: () => DEFAULT_PALETTE,\n qcColor: 'var(--mint-sample-9)',\n canDownloadTemplate: true,\n loading: false,\n renamable: false,\n})\n\nconst emit = defineEmits<{\n apply: [payload: FieldRecipeApplyPayload]\n cancel: []\n manual: []\n close: []\n /** Controlled mode: a field toggle was clicked (card index). */\n toggle: [index: number]\n /** A field moved one layer (card index). A drag over several layers emits one event for each step. */\n move: [index: number, direction: 'up' | 'down']\n /** The user gave a field a new name (card index). */\n rename: [index: number, name: string]\n /** Controlled mode: the QC routing select changed. */\n 'update:route': [route: QcRoute]\n /** A CSV/TSV file was chosen (upload button) or dropped; parent parses it. */\n 'csv-file': [file: File]\n /** Download a blank metadata template (parent calls the engine). */\n 'download-template': []\n /** Download a pre-filled metadata template (parent calls the engine). */\n 'download-prefilled': []\n}>()\n\n/** Tiny stroke-icon helper sharing the design's path data. */\nconst Icon: FunctionalComponent<{ d: string; size?: number }> = iconProps =>\n h(\n 'svg',\n {\n width: iconProps.size ?? 16,\n height: iconProps.size ?? 16,\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n 'stroke-width': 2,\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n },\n [h('path', { d: iconProps.d })],\n )\nconst ICON = {\n bolt: 'M13 10V3L4 14h7v7l9-11h-7z',\n info: 'M12 16v-4M12 8h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',\n warn: 'M12 9v4M12 17h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z',\n check: 'M20 6L9 17l-5-5',\n close: 'M6 18L18 6M6 6l12 12',\n upload: 'M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12',\n chev: 'M6 9l6 6 6-6',\n grip: 'M9 6h.01M9 12h.01M9 18h.01M15 6h.01M15 12h.01M15 18h.01',\n pencil: 'M12 20h9M16.5 3.5a2.12 2.12 0 013 3L7 19l-4 1 1-4L16.5 3.5z',\n}\n\nconst MAX_BAR = 44\n/** QC reads the same everywhere: neutral solid ring, never a group color (3a). */\nconst QC_DOT_STYLE = sampleTypeMarker('qc', '').style\n\n// CSV upload (presentational): the component only surfaces the control and\n// emits the chosen/dropped File; AutoGroupModalInternal parses it into the engine.\nconst fileInput = ref<HTMLInputElement | null>(null)\nconst dragOver = ref(false)\nlet dragDepth = 0\n\nfunction isCsvLike(file: File): boolean {\n return /\\.(csv|tsv)$/i.test(file.name)\n || file.type === 'text/csv'\n || file.type === 'text/tab-separated-values'\n}\nfunction pickCsv() {\n fileInput.value?.click()\n}\nfunction onFileChange(event: Event) {\n const input = event.target as HTMLInputElement\n const file = input.files?.[0]\n if (file) emit('csv-file', file)\n input.value = '' // allow re-selecting the same file\n}\nfunction onDragEnter(event: DragEvent) {\n // A field row dragged to a new layer is not a file drop.\n if (!event.dataTransfer?.types.includes('Files')) return\n dragDepth += 1\n dragOver.value = true\n}\nfunction onDragLeave() {\n dragDepth = Math.max(0, dragDepth - 1)\n if (dragDepth === 0) dragOver.value = false\n}\nfunction onDrop(event: DragEvent) {\n dragDepth = 0\n dragOver.value = false\n const file = event.dataTransfer?.files?.[0]\n if (file && isCsvLike(file)) emit('csv-file', file)\n}\n\nconst csvMenuItems = computed<ActionMenuItem[]>(() => [\n { id: 'upload', label: 'Upload CSV' },\n { id: 'template', label: 'Download template', disabled: !props.canDownloadTemplate },\n { id: 'prefilled', label: 'Download pre-filled', disabled: !props.canDownloadTemplate },\n])\nfunction onCsvMenuSelect(item: ActionMenuItem) {\n if (item.id === 'upload') pickCsv()\n else if (item.id === 'template') emit('download-template')\n else emit('download-prefilled')\n}\n\nconst QC_ROUTE_OPTIONS: { value: QcRoute; label: string }[] = [\n { value: 'Overlay only', label: 'Overlay' },\n { value: 'Exclude', label: 'Exclude' },\n { value: 'Mix into groups', label: 'Mix in' },\n]\n\n// Uncontrolled fallback state (used by the standalone story). In controlled\n// mode AutoGroupModalInternal supplies `enabled` / `layers` / `groups` / `route` +\n// `qcChip` and the `toggle` / `move` / `update:route` events drive the engine\n// instead of this local state. `orderInner` tracks the uncontrolled layer\n// order: field indices in toggle order, first entry = hierarchy layer 1.\n// `layers` without `enabled`: the parent owns the order, the toggles are local.\nconst startLayers = props.enabled === undefined ? props.layers : undefined\nconst onInner = ref<boolean[]>(\n props.initialOn\n ? [...props.initialOn]\n : startLayers?.map(layer => layer !== null) ?? props.fields.map(field => !field.rep),\n)\nconst orderInner = ref<number[]>(onInner.value.flatMap((isOn, i) => (isOn ? [i] : [])))\nconst routeInner = ref<QcRoute>(props.initialRoute)\n\nconst controlledEnabled = computed(() => props.enabled !== undefined)\nconst controlledRoute = computed(() => props.route !== undefined)\n\nconst on = computed(() => props.enabled ?? onInner.value)\nconst currentRoute = computed(() => props.route ?? routeInner.value)\n\n// Per-card hierarchy layer (1-based, null = not grouped by). Controlled mode\n// passes `layers` from the engine; controlled-without-layers keeps the legacy\n// field-order numbering; uncontrolled mode derives from the local toggle order.\nconst layers = computed<(number | null)[]>(() => {\n if (props.layers !== undefined && controlledEnabled.value) return props.layers\n if (props.layers !== undefined) {\n // Local toggles over the parent's order: a field that is switched off\n // leaves its layer, and one that is switched on goes last.\n let last = Math.max(0, ...props.layers.map(layer => layer ?? 0))\n return props.layers.map((layer, i) => (on.value[i] ? layer ?? ++last : null))\n }\n if (controlledEnabled.value) {\n let layer = 0\n return props.fields.map((_, i) => (on.value[i] ? ++layer : null))\n }\n return props.fields.map((_, i) => {\n const pos = orderInner.value.indexOf(i)\n return pos === -1 ? null : pos + 1\n })\n})\n// Field indices sorted by layer — the join order for names and the summary.\nconst layerOrder = computed<number[]>(() =>\n layers.value\n .flatMap((layer, i) => (layer === null ? [] : [{ layer, i }]))\n .sort((a, b) => a.layer - b.layer)\n .map(entry => entry.i),\n)\n\n// The two lists of the layout: grouped fields in layer order, then the rest.\n// `index` is the card index that the `toggle` / `move` / `rename` events carry.\ninterface FieldCard { index: number; field: SmartGroupField }\nconst onCards = computed<FieldCard[]>(() => layerOrder.value.map(index => ({ index, field: props.fields[index] })))\nconst offCards = computed<FieldCard[]>(() =>\n props.fields.flatMap((field, index) => (layers.value[index] === null ? [{ index, field }] : [])),\n)\n\nconst computedBase = computed(() =>\n computeFieldGroups(props.samples, props.fields, on.value, props.palette, layerOrder.value),\n)\nconst baseGroups = computed(() => props.groups ?? computedBase.value)\nconst groups = computed(() => props.groups ?? appendQcGroup(computedBase.value, currentRoute.value, props.qcColor))\nconst activeFactors = computed(() => layerOrder.value.map(i => props.fields[i].key))\nconst counts = computed(() => baseGroups.value.map(group => group.count))\nconst uniform = computed(() => counts.value.length > 0 && counts.value.every(count => count === counts.value[0]))\nconst repOnField = computed(() => props.fields.find((field, i) => field.rep && on.value[i]))\nconst repOn = computed(() => repOnField.value !== undefined)\n\nconst summary = computed(() =>\n fieldRecipeSummary({ activeFactors: activeFactors.value, repOn: repOn.value, baseGroups: baseGroups.value }),\n)\nconst groupedSampleTotal = computed(() => groups.value.reduce((sum, group) => sum + group.count, 0))\nconst maxCount = computed(() => Math.max(...groups.value.map(group => group.count), 1))\nconst qcCount = computed(() => props.qcCount ?? Math.max(0, props.headerCount - props.samples.length))\nconst realCount = computed(() => Math.max(0, props.headerCount - qcCount.value))\nconst sampleTotal = computed(() => sampleTotalFor(currentRoute.value, realCount.value, qcCount.value))\nconst qcChips = computed<FieldRecipeQcChip[]>(() => {\n if (props.qcChips !== undefined) return props.qcChips\n if (props.qcChip !== undefined) return props.qcChip ? [props.qcChip] : []\n return currentRoute.value !== 'Mix into groups' ? [{ name: 'QC_Pool', count: qcCount.value }] : []\n})\nconst footHint = computed(() =>\n currentRoute.value === 'Exclude'\n ? ` · ${qcCount.value} QC excluded`\n : currentRoute.value === 'Mix into groups'\n ? ' · QC mixed in'\n : ` · ${qcCount.value} QC overlaid`,\n)\n\n// A toggle moves the row to the other list, a keyboard move reorders the rows\n// and a rename replaces the row. Each time the focused control is re-created,\n// so focus goes back to the same control of the same field.\nconst root = ref<HTMLElement | null>(null)\nconst controlOf = (index: number, selector: string): HTMLElement | null =>\n root.value?.querySelector<HTMLElement>(`[data-field=\"${index}\"] ${selector}`) ?? null\nasync function refocus(index: number, selector: string) {\n await nextTick()\n controlOf(index, selector)?.focus()\n}\n\nfunction toggle(index: number) {\n void refocus(index, '[role=\"switch\"]')\n if (controlledEnabled.value) {\n emit('toggle', index)\n return\n }\n const wasOn = onInner.value[index]\n onInner.value = onInner.value.map((value, i) => (i === index ? !value : value))\n // A re-enabled field appends as the LAST layer, mirroring the engine.\n orderInner.value = wasOn\n ? orderInner.value.filter(i => i !== index)\n : [...orderInner.value, index]\n}\n\nfunction moveLayer(index: number, direction: 'up' | 'down') {\n if (controlledEnabled.value || props.layers !== undefined) {\n emit('move', index, direction)\n return\n }\n const from = orderInner.value.indexOf(index)\n if (from === -1) return\n const to = direction === 'up' ? from - 1 : from + 1\n if (to < 0 || to >= orderInner.value.length) return\n const next = [...orderInner.value]\n ;[next[from], next[to]] = [next[to], next[from]]\n orderInner.value = next\n}\n\n// Drag a grouped field to another layer, or press Alt + arrow on its handle.\nconst reorder = useListReorder<FieldCard>({\n items: () => onCards.value,\n getId: card => card.field.key,\n getLabel: card => card.field.key,\n itemNoun: 'layer',\n getElement: id => {\n const card = onCards.value.find(candidate => candidate.field.key === id)\n return card ? controlOf(card.index, '.fr-row__grip') : null\n },\n onReorder: ({ fromIndex, toIndex }) => {\n const { index } = onCards.value[fromIndex]\n const direction = toIndex < fromIndex ? 'up' : 'down'\n for (let step = 0; step < Math.abs(toIndex - fromIndex); step++) moveLayer(index, direction)\n },\n})\n\n// Inline rename of one field at a time. The name is the identity of a field,\n// so a name that another field has is refused and the input stays open.\nconst renaming = ref<number | null>(null)\nconst draftName = ref('')\nconst renameTaken = ref(false)\nconst renameInput = ref<HTMLInputElement[]>([])\nasync function startRename(card: FieldCard) {\n renaming.value = card.index\n renameTaken.value = false\n draftName.value = card.field.key\n await nextTick()\n renameInput.value[0]?.select()\n}\nfunction endRename(card: FieldCard, restoreFocus: boolean) {\n renaming.value = null\n renameTaken.value = false\n if (restoreFocus) void refocus(card.index, '.fr-row__rename')\n}\nfunction commitRename(card: FieldCard, fromBlur = false) {\n if (renaming.value !== card.index) return\n const name = draftName.value.trim()\n const taken = name !== card.field.key\n && (props.fields.some(field => field.key === name) || (props.reservedNames?.includes(name) ?? false))\n if (taken && !fromBlur) {\n renameTaken.value = true\n return\n }\n // Leaving the input drops a name that is in use; focus stays where the user put it.\n endRename(card, !fromBlur)\n if (name && name !== card.field.key && !taken) emit('rename', card.index, name)\n}\n\nfunction onRouteChange(value: QcRoute) {\n if (controlledRoute.value) {\n emit('update:route', value)\n return\n }\n routeInner.value = value\n}\n\nfunction onApply() {\n if (activeFactors.value.length === 0) return\n emit('apply', {\n groups: groups.value,\n route: currentRoute.value,\n sampleTotal: sampleTotal.value,\n activeFactors: activeFactors.value,\n })\n}\n</script>\n\n<template>\n <div ref=\"root\" class=\"sg-modal fr\">\n <!-- header -->\n <div class=\"sg-head\">\n <Icon :d=\"ICON.bolt\" :size=\"18\" />\n <span class=\"sg-head__title\">Group samples</span>\n <span class=\"sg-head__sub\">· {{ headerCount }} samples<template v-if=\"experimentLabel\"> from {{ experimentLabel }}</template></span>\n <div class=\"sg-head__spacer\" />\n <ActionMenu label=\"CSV\" :items=\"csvMenuItems\" @select=\"onCsvMenuSelect\">\n <template #trigger=\"{ open, toggle: toggleMenu, keydown }\">\n <button\n type=\"button\"\n class=\"fr-upload\"\n aria-haspopup=\"menu\"\n :aria-expanded=\"open\"\n @click=\"toggleMenu\"\n @keydown=\"keydown\"\n >\n <Icon :d=\"ICON.upload\" :size=\"14\" /> CSV <Icon :d=\"ICON.chev\" :size=\"13\" />\n </button>\n </template>\n </ActionMenu>\n <input\n ref=\"fileInput\"\n type=\"file\"\n accept=\".csv,.tsv,text/csv,text/tab-separated-values\"\n hidden\n @change=\"onFileChange\"\n />\n <div class=\"sg-mode\" role=\"tablist\" aria-label=\"Grouping mode\">\n <button type=\"button\" class=\"sg-mode__btn sg-mode__btn--on\" role=\"tab\" :aria-selected=\"true\">Auto</button>\n <button type=\"button\" class=\"sg-mode__btn\" role=\"tab\" :aria-selected=\"false\" @click=\"emit('manual')\">Manual</button>\n </div>\n <button class=\"sg-head__close\" aria-label=\"Close\" @click=\"emit('close')\">\n <Icon :d=\"ICON.close\" :size=\"16\" />\n </button>\n </div>\n\n <div class=\"sg-body\">\n <!-- main -->\n <div\n class=\"sg-body__main\"\n @dragenter.prevent=\"onDragEnter\"\n @dragover.prevent\n @dragleave.prevent=\"onDragLeave\"\n @drop.prevent=\"onDrop\"\n >\n <div v-if=\"dragOver\" class=\"fr-dropzone\">\n <Icon :d=\"ICON.upload\" :size=\"22\" />\n <span>Drop CSV to load samples</span>\n </div>\n <div v-if=\"loading\" class=\"fr-loading\">\n <LoadingSpinner size=\"md\" label=\"Loading experiment metadata\" />\n <span>Loading experiment metadata…</span>\n </div>\n <div v-if=\"fields.length === 0 && headerCount === 0 && !loading\" class=\"fr-blank\" data-test=\"recipe-empty\">\n <span class=\"sg-lead__q\">No samples to group</span>\n <span class=\"sg-lead__hint\">\n Upload a CSV with one sample column and one column for each field, or close this dialog and select\n samples first.\n </span>\n <BaseButton variant=\"secondary\" size=\"sm\" @click=\"pickCsv\">\n <template #icon-left><Icon :d=\"ICON.upload\" :size=\"15\" /></template>\n Upload CSV\n </BaseButton>\n </div>\n <template v-else>\n <div v-if=\"anatomy && anatomy.length > 0\" class=\"fr-anatomy\">\n <span class=\"fr-label\">How MINT read your names</span>\n <div class=\"fr-anatomy__tokens\">\n <span v-for=\"(token, t) in anatomy\" :key=\"t\" class=\"fr-token\" :class=\"'fr-token--' + token.state\">\n <span class=\"fr-token__text\">{{ token.text }}</span>\n <span class=\"fr-token__caption\">{{ token.caption }}</span>\n </span>\n </div>\n </div>\n\n <span class=\"fr-label\">Group by</span>\n <div class=\"sg-recipe\">\n <div v-if=\"onCards.length === 0\" class=\"fr-empty\">\n {{ fields.length === 0 ? 'These sample names have no field to group by.' : 'No field is on. Switch on a field below.' }}\n </div>\n <div\n v-for=\"(card, position) in onCards\"\n :key=\"card.field.key\"\n class=\"sg-field sg-field--on fr-row\"\n :class=\"{\n 'fr-field--warn': card.field.rep,\n 'fr-row--dragging': reorder.isDragging(card.field.key),\n 'fr-row--over': reorder.isDragOver(card.field.key),\n }\"\n data-test=\"field-on\"\n :data-field=\"card.index\"\n :draggable=\"renaming !== card.index\"\n @dragstart=\"reorder.onDragStart($event, card, position)\"\n @dragover=\"reorder.onDragOver($event, card, position)\"\n @dragleave=\"reorder.onDragLeave\"\n @drop=\"reorder.onDrop($event, card, position)\"\n @dragend=\"reorder.onDragEnd\"\n >\n <button\n type=\"button\"\n class=\"fr-row__grip\"\n :aria-label=\"`Move ${card.field.key}, layer ${position + 1} of ${onCards.length}. Press Alt and an arrow key.`\"\n @keydown=\"reorder.onKeydown($event, card, position)\"\n >\n <Icon :d=\"ICON.grip\" :size=\"14\" />\n </button>\n <span class=\"fr-row__layer\">{{ position + 1 }}</span>\n <input\n v-if=\"renaming === card.index\"\n ref=\"renameInput\"\n v-model=\"draftName\"\n class=\"fr-row__name-input\"\n :aria-label=\"'New name for ' + card.field.key\"\n :aria-invalid=\"renameTaken || undefined\"\n @input=\"renameTaken = false\"\n @keydown.enter.prevent=\"commitRename(card)\"\n @keydown.esc.stop=\"endRename(card, true)\"\n @blur=\"commitRename(card, true)\"\n />\n <span v-if=\"renaming === card.index && renameTaken\" class=\"fr-row__error\" role=\"alert\">\n Another field has this name\n </span>\n <template v-else>\n <span class=\"sg-field__name\">{{ card.field.key }}</span>\n <button\n v-if=\"renamable\"\n type=\"button\"\n class=\"fr-row__rename\"\n :aria-label=\"'Rename ' + card.field.key\"\n @click=\"startRename(card)\"\n >\n <Icon :d=\"ICON.pencil\" :size=\"12\" />\n </button>\n </template>\n <span class=\"sg-field__vals\">{{ card.field.vals }}</span>\n <span v-if=\"hints?.[card.index]\" class=\"fr-row__hint\">{{ hints[card.index] }}</span>\n <BaseCheckbox\n type=\"switch\"\n size=\"sm\"\n :model-value=\"true\"\n :aria-label=\"'Group by ' + card.field.key\"\n @update:model-value=\"toggle(card.index)\"\n />\n </div>\n </div>\n <span :style=\"reorder.announcerStyle\" aria-live=\"polite\">{{ reorder.announcement.value }}</span>\n\n <template v-if=\"offCards.length > 0\">\n <span class=\"fr-label\">Left off</span>\n <div class=\"sg-recipe\">\n <div\n v-for=\"card in offCards\"\n :key=\"card.field.key\"\n class=\"sg-field fr-row\"\n data-test=\"field-off\"\n :data-field=\"card.index\"\n >\n <span class=\"sg-field__name\">{{ card.field.key }}</span>\n <span class=\"sg-field__role\" :class=\"{ 'sg-field__role--rep': card.field.rep }\">{{ card.field.role }}</span>\n <span class=\"sg-field__vals\">{{ card.field.vals }}</span>\n <BaseCheckbox\n type=\"switch\"\n size=\"sm\"\n :model-value=\"false\"\n :aria-label=\"'Group by ' + card.field.key\"\n @update:model-value=\"toggle(card.index)\"\n />\n </div>\n </div>\n </template>\n\n <!-- inline plain-english summary -->\n <div v-if=\"summary.kind === 'mute'\" class=\"sg-summary fr-summary--mute\">\n <span class=\"sg-summary__icon\"><Icon :d=\"ICON.info\" :size=\"16\" /></span>\n <span>Turn on at least one field to form groups.</span>\n </div>\n <div v-else-if=\"summary.kind === 'warn'\" class=\"sg-summary sg-summary--warn\">\n <span class=\"sg-summary__icon\"><Icon :d=\"ICON.warn\" :size=\"16\" /></span>\n <span>\n Including <b>{{ repOnField?.key }}</b> makes every sample unique —\n <b>{{ baseGroups.length }} groups of 1</b>. Switch it off to keep replicates together.\n </span>\n </div>\n <div v-else-if=\"summary.kind === 'singletons'\" class=\"sg-summary sg-summary--warn\">\n <span class=\"sg-summary__icon\"><Icon :d=\"ICON.warn\" :size=\"16\" /></span>\n <span>\n <b>{{ counts.filter(count => count === 1).length }} of {{ baseGroups.length }} groups</b> have one sample.\n Switch off a field to form larger groups.\n </span>\n </div>\n <div v-else class=\"sg-summary\">\n <span class=\"sg-summary__icon\"><Icon :d=\"ICON.info\" :size=\"16\" /></span>\n <span>\n Grouping by <b>{{ activeFactors.join(' × ') }}</b> →\n <b>{{ baseGroups.length }} groups<template v-if=\"uniform\"> of {{ counts[0] }}</template></b>.\n </span>\n </div>\n </template>\n </div>\n\n <!-- rail -->\n <div class=\"sg-body__side\">\n <div class=\"sg-preview__head\">\n Resulting groups\n <span class=\"sg-preview__count\">{{ groups.length }} groups · {{ groupedSampleTotal }} samples</span>\n </div>\n <div class=\"sg-grouplist\">\n <div v-if=\"groups.length === 0\" class=\"fr-empty\">No groups yet — pick a field to start.</div>\n <div v-for=\"group in groups\" v-else :key=\"group.name\" class=\"sg-group\">\n <span class=\"sg-dot\" :style=\"group.name === QC_POOL_NAME ? QC_DOT_STYLE : { background: group.color }\" />\n <span class=\"sg-group__name\">{{ group.name }}</span>\n <span\n class=\"sg-group__bar\"\n :style=\"{ width: (MAX_BAR * group.count) / maxCount + 'px', '--g': group.color }\"\n />\n <span class=\"sg-group__count\">{{ group.count }}</span>\n </div>\n </div>\n\n <div class=\"sg-qc\">\n <div class=\"sg-qc__head\">\n <span class=\"sg-qc__title\">Quality control</span>\n <SegmentedControl\n class=\"fr-qc__route\"\n size=\"sm\"\n variant=\"card\"\n aria-label=\"Quality control routing\"\n :model-value=\"currentRoute\"\n :options=\"QC_ROUTE_OPTIONS\"\n @update:model-value=\"onRouteChange($event as QcRoute)\"\n />\n </div>\n <div class=\"fr-qc__chips\">\n <span\n v-for=\"chip in qcChips\"\n :key=\"chip.name\"\n class=\"sg-qc__chip\"\n :class=\"{ 'fr-qc__chip--off': currentRoute === 'Exclude' }\"\n >\n <span class=\"sg-dot\" :style=\"QC_DOT_STYLE\" />{{ chip.name }} ×{{ chip.count }}\n </span>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"sg-foot\">\n <span class=\"sg-foot__hint\">Applies to {{ sampleTotal }} samples{{ footHint }}</span>\n <div class=\"sg-foot__spacer\" />\n <BaseButton variant=\"ghost\" size=\"sm\" @click=\"emit('cancel')\">Cancel</BaseButton>\n <BaseButton variant=\"primary\" size=\"sm\" :disabled=\"activeFactors.length === 0\" @click=\"onApply\">\n <template #icon-left><Icon :d=\"ICON.check\" :size=\"15\" /></template>\n Apply grouping\n </BaseButton>\n </div>\n </div>\n</template>\n","<script lang=\"ts\">\n/**\n * SmartGroupFieldRecipe — the \"auto\" mode of the Smart Group modal.\n *\n * MINT parses the structure of sample names into candidate fields; the user\n * switches on the factors that define their groups. A live preview rail\n * reflects the resulting groups, an inline summary explains what will happen\n * (including the replicate \"singleton trap\"), and a QC routing control decides\n * how quality-control samples are handled. Grouping maths live in the tested\n * `./SmartGroupFieldRecipe.groups` module.\n *\n * The reference-fixture defaults live in this module-scope `<script>` block\n * because `defineProps` default factories are hoisted and cannot reference\n * bindings declared inside `<script setup>`.\n */\nimport type { SmartGroupField, SmartGroupSampleRecord } from './SmartGroupFieldRecipe.groups'\nimport { SAMPLE_GROUP_TOKENS } from '../utils/color'\n\nconst FR_BASE = [\n { Condition: 'Control', Dose: 'Vehicle' },\n { Condition: 'Treatment', Dose: 'Low' },\n { Condition: 'Treatment', Dose: 'High' },\n]\nconst DEFAULT_SAMPLES: SmartGroupSampleRecord[] = FR_BASE.flatMap(base =>\n [1, 2, 3].map(rep => ({ ...base, Replicate: `Rep${rep}` })),\n)\nconst DEFAULT_FIELDS: SmartGroupField[] = [\n { key: 'Condition', role: 'Factor', vals: 'Control, Treatment', rep: false },\n { key: 'Dose', role: 'Factor', vals: 'Vehicle, Low, High', rep: false },\n { key: 'Replicate', role: 'Replicate', vals: 'Rep1, Rep2, Rep3', rep: true },\n]\nconst DEFAULT_PALETTE = [...SAMPLE_GROUP_TOKENS]\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, h, nextTick, ref, type FunctionalComponent } from 'vue'\nimport {\n appendQcGroup,\n computeFieldGroups,\n fieldRecipeSummary,\n QC_POOL_NAME,\n sampleTotalFor,\n type QcRoute,\n} from './SmartGroupFieldRecipe.groups'\nimport { sampleTypeMarker } from '../utils/sampleTypeMarker'\nimport { useListReorder } from '../composables/useListReorder'\nimport ActionMenu from './ActionMenu.vue'\nimport BaseButton from './BaseButton.vue'\nimport BaseCheckbox from './BaseCheckbox.vue'\nimport LoadingSpinner from './LoadingSpinner.vue'\nimport SegmentedControl from './SegmentedControl.vue'\nimport type { ActionMenuItem } from '../types/components'\nimport type { FieldRecipeApplyPayload, FieldRecipeQcChip, SmartGroupFieldRecipeProps } from './SmartGroup.types'\n\nconst props = withDefaults(defineProps<SmartGroupFieldRecipeProps>(), {\n samples: () => DEFAULT_SAMPLES,\n fields: () => DEFAULT_FIELDS,\n experimentLabel: 'EXP-014',\n headerCount: 12,\n initialRoute: 'Overlay only',\n palette: () => DEFAULT_PALETTE,\n qcColor: 'var(--mint-sample-9)',\n canDownloadTemplate: true,\n loading: false,\n renamable: false,\n})\n\nconst emit = defineEmits<{\n apply: [payload: FieldRecipeApplyPayload]\n cancel: []\n manual: []\n close: []\n /** Controlled mode: a field toggle was clicked (card index). */\n toggle: [index: number]\n /** A field moved one layer (card index). A drag over several layers emits one event for each step. */\n move: [index: number, direction: 'up' | 'down']\n /** The user gave a field a new name (card index). */\n rename: [index: number, name: string]\n /** Controlled mode: the QC routing select changed. */\n 'update:route': [route: QcRoute]\n /** A CSV/TSV file was chosen (upload button) or dropped; parent parses it. */\n 'csv-file': [file: File]\n /** Download a blank metadata template (parent calls the engine). */\n 'download-template': []\n /** Download a pre-filled metadata template (parent calls the engine). */\n 'download-prefilled': []\n}>()\n\n/** Tiny stroke-icon helper sharing the design's path data. */\nconst Icon: FunctionalComponent<{ d: string; size?: number }> = iconProps =>\n h(\n 'svg',\n {\n width: iconProps.size ?? 16,\n height: iconProps.size ?? 16,\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n 'stroke-width': 2,\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n },\n [h('path', { d: iconProps.d })],\n )\nconst ICON = {\n bolt: 'M13 10V3L4 14h7v7l9-11h-7z',\n info: 'M12 16v-4M12 8h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z',\n warn: 'M12 9v4M12 17h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z',\n check: 'M20 6L9 17l-5-5',\n close: 'M6 18L18 6M6 6l12 12',\n upload: 'M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12',\n chev: 'M6 9l6 6 6-6',\n grip: 'M9 6h.01M9 12h.01M9 18h.01M15 6h.01M15 12h.01M15 18h.01',\n pencil: 'M12 20h9M16.5 3.5a2.12 2.12 0 013 3L7 19l-4 1 1-4L16.5 3.5z',\n}\n\nconst MAX_BAR = 44\n/** QC reads the same everywhere: neutral solid ring, never a group color (3a). */\nconst QC_DOT_STYLE = sampleTypeMarker('qc', '').style\n\n// CSV upload (presentational): the component only surfaces the control and\n// emits the chosen/dropped File; AutoGroupModalInternal parses it into the engine.\nconst fileInput = ref<HTMLInputElement | null>(null)\nconst dragOver = ref(false)\nlet dragDepth = 0\n\nfunction isCsvLike(file: File): boolean {\n return /\\.(csv|tsv)$/i.test(file.name)\n || file.type === 'text/csv'\n || file.type === 'text/tab-separated-values'\n}\nfunction pickCsv() {\n fileInput.value?.click()\n}\nfunction onFileChange(event: Event) {\n const input = event.target as HTMLInputElement\n const file = input.files?.[0]\n if (file) emit('csv-file', file)\n input.value = '' // allow re-selecting the same file\n}\nfunction onDragEnter(event: DragEvent) {\n // A field row dragged to a new layer is not a file drop.\n if (!event.dataTransfer?.types.includes('Files')) return\n dragDepth += 1\n dragOver.value = true\n}\nfunction onDragLeave() {\n dragDepth = Math.max(0, dragDepth - 1)\n if (dragDepth === 0) dragOver.value = false\n}\nfunction onDrop(event: DragEvent) {\n dragDepth = 0\n dragOver.value = false\n const file = event.dataTransfer?.files?.[0]\n if (file && isCsvLike(file)) emit('csv-file', file)\n}\n\nconst csvMenuItems = computed<ActionMenuItem[]>(() => [\n { id: 'upload', label: 'Upload CSV' },\n { id: 'template', label: 'Download template', disabled: !props.canDownloadTemplate },\n { id: 'prefilled', label: 'Download pre-filled', disabled: !props.canDownloadTemplate },\n])\nfunction onCsvMenuSelect(item: ActionMenuItem) {\n if (item.id === 'upload') pickCsv()\n else if (item.id === 'template') emit('download-template')\n else emit('download-prefilled')\n}\n\nconst QC_ROUTE_OPTIONS: { value: QcRoute; label: string }[] = [\n { value: 'Overlay only', label: 'Overlay' },\n { value: 'Exclude', label: 'Exclude' },\n { value: 'Mix into groups', label: 'Mix in' },\n]\n\n// Uncontrolled fallback state (used by the standalone story). In controlled\n// mode AutoGroupModalInternal supplies `enabled` / `layers` / `groups` / `route` +\n// `qcChip` and the `toggle` / `move` / `update:route` events drive the engine\n// instead of this local state. `orderInner` tracks the uncontrolled layer\n// order: field indices in toggle order, first entry = hierarchy layer 1.\n// `layers` without `enabled`: the parent owns the order, the toggles are local.\nconst startLayers = props.enabled === undefined ? props.layers : undefined\nconst onInner = ref<boolean[]>(\n props.initialOn\n ? [...props.initialOn]\n : startLayers?.map(layer => layer !== null) ?? props.fields.map(field => !field.rep),\n)\nconst orderInner = ref<number[]>(onInner.value.flatMap((isOn, i) => (isOn ? [i] : [])))\nconst routeInner = ref<QcRoute>(props.initialRoute)\n\nconst controlledEnabled = computed(() => props.enabled !== undefined)\nconst controlledRoute = computed(() => props.route !== undefined)\n\nconst on = computed(() => props.enabled ?? onInner.value)\nconst currentRoute = computed(() => props.route ?? routeInner.value)\n\n// Per-card hierarchy layer (1-based, null = not grouped by). Controlled mode\n// passes `layers` from the engine; controlled-without-layers keeps the legacy\n// field-order numbering; uncontrolled mode derives from the local toggle order.\nconst layers = computed<(number | null)[]>(() => {\n if (props.layers !== undefined && controlledEnabled.value) return props.layers\n if (props.layers !== undefined) {\n // Local toggles over the parent's order: a field that is switched off\n // leaves its layer, and one that is switched on goes last.\n let last = Math.max(0, ...props.layers.map(layer => layer ?? 0))\n return props.layers.map((layer, i) => (on.value[i] ? layer ?? ++last : null))\n }\n if (controlledEnabled.value) {\n let layer = 0\n return props.fields.map((_, i) => (on.value[i] ? ++layer : null))\n }\n return props.fields.map((_, i) => {\n const pos = orderInner.value.indexOf(i)\n return pos === -1 ? null : pos + 1\n })\n})\n// Field indices sorted by layer — the join order for names and the summary.\nconst layerOrder = computed<number[]>(() =>\n layers.value\n .flatMap((layer, i) => (layer === null ? [] : [{ layer, i }]))\n .sort((a, b) => a.layer - b.layer)\n .map(entry => entry.i),\n)\n\n// The two lists of the layout: grouped fields in layer order, then the rest.\n// `index` is the card index that the `toggle` / `move` / `rename` events carry.\ninterface FieldCard { index: number; field: SmartGroupField }\nconst onCards = computed<FieldCard[]>(() => layerOrder.value.map(index => ({ index, field: props.fields[index] })))\nconst offCards = computed<FieldCard[]>(() =>\n props.fields.flatMap((field, index) => (layers.value[index] === null ? [{ index, field }] : [])),\n)\n\nconst computedBase = computed(() =>\n computeFieldGroups(props.samples, props.fields, on.value, props.palette, layerOrder.value),\n)\nconst baseGroups = computed(() => props.groups ?? computedBase.value)\nconst groups = computed(() => props.groups ?? appendQcGroup(computedBase.value, currentRoute.value, props.qcColor))\nconst activeFactors = computed(() => layerOrder.value.map(i => props.fields[i].key))\nconst counts = computed(() => baseGroups.value.map(group => group.count))\nconst uniform = computed(() => counts.value.length > 0 && counts.value.every(count => count === counts.value[0]))\nconst repOnField = computed(() => props.fields.find((field, i) => field.rep && on.value[i]))\nconst repOn = computed(() => repOnField.value !== undefined)\n\nconst summary = computed(() =>\n fieldRecipeSummary({ activeFactors: activeFactors.value, repOn: repOn.value, baseGroups: baseGroups.value }),\n)\nconst groupedSampleTotal = computed(() => groups.value.reduce((sum, group) => sum + group.count, 0))\nconst maxCount = computed(() => Math.max(...groups.value.map(group => group.count), 1))\nconst qcCount = computed(() => props.qcCount ?? Math.max(0, props.headerCount - props.samples.length))\nconst realCount = computed(() => Math.max(0, props.headerCount - qcCount.value))\nconst sampleTotal = computed(() => sampleTotalFor(currentRoute.value, realCount.value, qcCount.value))\nconst qcChips = computed<FieldRecipeQcChip[]>(() => {\n if (props.qcChips !== undefined) return props.qcChips\n if (props.qcChip !== undefined) return props.qcChip ? [props.qcChip] : []\n return currentRoute.value !== 'Mix into groups' ? [{ name: 'QC_Pool', count: qcCount.value }] : []\n})\nconst footHint = computed(() =>\n currentRoute.value === 'Exclude'\n ? ` · ${qcCount.value} QC excluded`\n : currentRoute.value === 'Mix into groups'\n ? ' · QC mixed in'\n : ` · ${qcCount.value} QC overlaid`,\n)\n\n// A toggle moves the row to the other list, a keyboard move reorders the rows\n// and a rename replaces the row. Each time the focused control is re-created,\n// so focus goes back to the same control of the same field.\nconst root = ref<HTMLElement | null>(null)\nconst controlOf = (index: number, selector: string): HTMLElement | null =>\n root.value?.querySelector<HTMLElement>(`[data-field=\"${index}\"] ${selector}`) ?? null\nasync function refocus(index: number, selector: string) {\n await nextTick()\n controlOf(index, selector)?.focus()\n}\n\nfunction toggle(index: number) {\n void refocus(index, '[role=\"switch\"]')\n if (controlledEnabled.value) {\n emit('toggle', index)\n return\n }\n const wasOn = onInner.value[index]\n onInner.value = onInner.value.map((value, i) => (i === index ? !value : value))\n // A re-enabled field appends as the LAST layer, mirroring the engine.\n orderInner.value = wasOn\n ? orderInner.value.filter(i => i !== index)\n : [...orderInner.value, index]\n}\n\nfunction moveLayer(index: number, direction: 'up' | 'down') {\n if (controlledEnabled.value || props.layers !== undefined) {\n emit('move', index, direction)\n return\n }\n const from = orderInner.value.indexOf(index)\n if (from === -1) return\n const to = direction === 'up' ? from - 1 : from + 1\n if (to < 0 || to >= orderInner.value.length) return\n const next = [...orderInner.value]\n ;[next[from], next[to]] = [next[to], next[from]]\n orderInner.value = next\n}\n\n// Drag a grouped field to another layer, or press Alt + arrow on its handle.\nconst reorder = useListReorder<FieldCard>({\n items: () => onCards.value,\n getId: card => card.field.key,\n getLabel: card => card.field.key,\n itemNoun: 'layer',\n getElement: id => {\n const card = onCards.value.find(candidate => candidate.field.key === id)\n return card ? controlOf(card.index, '.fr-row__grip') : null\n },\n onReorder: ({ fromIndex, toIndex }) => {\n const { index } = onCards.value[fromIndex]\n const direction = toIndex < fromIndex ? 'up' : 'down'\n for (let step = 0; step < Math.abs(toIndex - fromIndex); step++) moveLayer(index, direction)\n },\n})\n\n// Inline rename of one field at a time. The name is the identity of a field,\n// so a name that another field has is refused and the input stays open.\nconst renaming = ref<number | null>(null)\nconst draftName = ref('')\nconst renameTaken = ref(false)\nconst renameInput = ref<HTMLInputElement[]>([])\nasync function startRename(card: FieldCard) {\n renaming.value = card.index\n renameTaken.value = false\n draftName.value = card.field.key\n await nextTick()\n renameInput.value[0]?.select()\n}\nfunction endRename(card: FieldCard, restoreFocus: boolean) {\n renaming.value = null\n renameTaken.value = false\n if (restoreFocus) void refocus(card.index, '.fr-row__rename')\n}\nfunction commitRename(card: FieldCard, fromBlur = false) {\n if (renaming.value !== card.index) return\n const name = draftName.value.trim()\n const taken = name !== card.field.key\n && (props.fields.some(field => field.key === name) || (props.reservedNames?.includes(name) ?? false))\n if (taken && !fromBlur) {\n renameTaken.value = true\n return\n }\n // Leaving the input drops a name that is in use; focus stays where the user put it.\n endRename(card, !fromBlur)\n if (name && name !== card.field.key && !taken) emit('rename', card.index, name)\n}\n\nfunction onRouteChange(value: QcRoute) {\n if (controlledRoute.value) {\n emit('update:route', value)\n return\n }\n routeInner.value = value\n}\n\nfunction onApply() {\n if (activeFactors.value.length === 0) return\n emit('apply', {\n groups: groups.value,\n route: currentRoute.value,\n sampleTotal: sampleTotal.value,\n activeFactors: activeFactors.value,\n })\n}\n</script>\n\n<template>\n <div ref=\"root\" class=\"sg-modal fr\">\n <!-- header -->\n <div class=\"sg-head\">\n <Icon :d=\"ICON.bolt\" :size=\"18\" />\n <span class=\"sg-head__title\">Group samples</span>\n <span class=\"sg-head__sub\">· {{ headerCount }} samples<template v-if=\"experimentLabel\"> from {{ experimentLabel }}</template></span>\n <div class=\"sg-head__spacer\" />\n <ActionMenu label=\"CSV\" :items=\"csvMenuItems\" @select=\"onCsvMenuSelect\">\n <template #trigger=\"{ open, toggle: toggleMenu, keydown }\">\n <button\n type=\"button\"\n class=\"fr-upload\"\n aria-haspopup=\"menu\"\n :aria-expanded=\"open\"\n @click=\"toggleMenu\"\n @keydown=\"keydown\"\n >\n <Icon :d=\"ICON.upload\" :size=\"14\" /> CSV <Icon :d=\"ICON.chev\" :size=\"13\" />\n </button>\n </template>\n </ActionMenu>\n <input\n ref=\"fileInput\"\n type=\"file\"\n accept=\".csv,.tsv,text/csv,text/tab-separated-values\"\n hidden\n @change=\"onFileChange\"\n />\n <div class=\"sg-mode\" role=\"tablist\" aria-label=\"Grouping mode\">\n <button type=\"button\" class=\"sg-mode__btn sg-mode__btn--on\" role=\"tab\" :aria-selected=\"true\">Auto</button>\n <button type=\"button\" class=\"sg-mode__btn\" role=\"tab\" :aria-selected=\"false\" @click=\"emit('manual')\">Manual</button>\n </div>\n <button class=\"sg-head__close\" aria-label=\"Close\" @click=\"emit('close')\">\n <Icon :d=\"ICON.close\" :size=\"16\" />\n </button>\n </div>\n\n <div class=\"sg-body\">\n <!-- main -->\n <div\n class=\"sg-body__main\"\n @dragenter.prevent=\"onDragEnter\"\n @dragover.prevent\n @dragleave.prevent=\"onDragLeave\"\n @drop.prevent=\"onDrop\"\n >\n <div v-if=\"dragOver\" class=\"fr-dropzone\">\n <Icon :d=\"ICON.upload\" :size=\"22\" />\n <span>Drop CSV to load samples</span>\n </div>\n <div v-if=\"loading\" class=\"fr-loading\">\n <LoadingSpinner size=\"md\" label=\"Loading experiment metadata\" />\n <span>Loading experiment metadata…</span>\n </div>\n <div v-if=\"fields.length === 0 && headerCount === 0 && !loading\" class=\"fr-blank\" data-test=\"recipe-empty\">\n <span class=\"sg-lead__q\">No samples to group</span>\n <span class=\"sg-lead__hint\">\n Upload a CSV with one sample column and one column for each field, or close this dialog and select\n samples first.\n </span>\n <BaseButton variant=\"secondary\" size=\"sm\" @click=\"pickCsv\">\n <template #icon-left><Icon :d=\"ICON.upload\" :size=\"15\" /></template>\n Upload CSV\n </BaseButton>\n </div>\n <template v-else>\n <div v-if=\"anatomy && anatomy.length > 0\" class=\"fr-anatomy\">\n <span class=\"fr-label\">How MINT read your names</span>\n <div class=\"fr-anatomy__tokens\">\n <span v-for=\"(token, t) in anatomy\" :key=\"t\" class=\"fr-token\" :class=\"'fr-token--' + token.state\">\n <span class=\"fr-token__text\">{{ token.text }}</span>\n <span class=\"fr-token__caption\">{{ token.caption }}</span>\n </span>\n </div>\n </div>\n\n <span class=\"fr-label\">Group by</span>\n <div class=\"sg-recipe\">\n <div v-if=\"onCards.length === 0\" class=\"fr-empty\">\n {{ fields.length === 0 ? 'These sample names have no field to group by.' : 'No field is on. Switch on a field below.' }}\n </div>\n <div\n v-for=\"(card, position) in onCards\"\n :key=\"card.field.key\"\n class=\"sg-field sg-field--on fr-row\"\n :class=\"{\n 'fr-field--warn': card.field.rep,\n 'fr-row--dragging': reorder.isDragging(card.field.key),\n 'fr-row--over': reorder.isDragOver(card.field.key),\n }\"\n data-test=\"field-on\"\n :data-field=\"card.index\"\n :draggable=\"renaming !== card.index\"\n @dragstart=\"reorder.onDragStart($event, card, position)\"\n @dragover=\"reorder.onDragOver($event, card, position)\"\n @dragleave=\"reorder.onDragLeave\"\n @drop=\"reorder.onDrop($event, card, position)\"\n @dragend=\"reorder.onDragEnd\"\n >\n <button\n type=\"button\"\n class=\"fr-row__grip\"\n :aria-label=\"`Move ${card.field.key}, layer ${position + 1} of ${onCards.length}. Press Alt and an arrow key.`\"\n @keydown=\"reorder.onKeydown($event, card, position)\"\n >\n <Icon :d=\"ICON.grip\" :size=\"14\" />\n </button>\n <span class=\"fr-row__layer\">{{ position + 1 }}</span>\n <input\n v-if=\"renaming === card.index\"\n ref=\"renameInput\"\n v-model=\"draftName\"\n class=\"fr-row__name-input\"\n :aria-label=\"'New name for ' + card.field.key\"\n :aria-invalid=\"renameTaken || undefined\"\n @input=\"renameTaken = false\"\n @keydown.enter.prevent=\"commitRename(card)\"\n @keydown.esc.stop=\"endRename(card, true)\"\n @blur=\"commitRename(card, true)\"\n />\n <span v-if=\"renaming === card.index && renameTaken\" class=\"fr-row__error\" role=\"alert\">\n Another field has this name\n </span>\n <template v-else>\n <span class=\"sg-field__name\">{{ card.field.key }}</span>\n <button\n v-if=\"renamable\"\n type=\"button\"\n class=\"fr-row__rename\"\n :aria-label=\"'Rename ' + card.field.key\"\n @click=\"startRename(card)\"\n >\n <Icon :d=\"ICON.pencil\" :size=\"12\" />\n </button>\n </template>\n <span class=\"sg-field__vals\">{{ card.field.vals }}</span>\n <span v-if=\"hints?.[card.index]\" class=\"fr-row__hint\">{{ hints[card.index] }}</span>\n <BaseCheckbox\n type=\"switch\"\n size=\"sm\"\n :model-value=\"true\"\n :aria-label=\"'Group by ' + card.field.key\"\n @update:model-value=\"toggle(card.index)\"\n />\n </div>\n </div>\n <span :style=\"reorder.announcerStyle\" aria-live=\"polite\">{{ reorder.announcement.value }}</span>\n\n <template v-if=\"offCards.length > 0\">\n <span class=\"fr-label\">Left off</span>\n <div class=\"sg-recipe\">\n <div\n v-for=\"card in offCards\"\n :key=\"card.field.key\"\n class=\"sg-field fr-row\"\n data-test=\"field-off\"\n :data-field=\"card.index\"\n >\n <span class=\"sg-field__name\">{{ card.field.key }}</span>\n <span class=\"sg-field__role\" :class=\"{ 'sg-field__role--rep': card.field.rep }\">{{ card.field.role }}</span>\n <span class=\"sg-field__vals\">{{ card.field.vals }}</span>\n <BaseCheckbox\n type=\"switch\"\n size=\"sm\"\n :model-value=\"false\"\n :aria-label=\"'Group by ' + card.field.key\"\n @update:model-value=\"toggle(card.index)\"\n />\n </div>\n </div>\n </template>\n\n <!-- inline plain-english summary -->\n <div v-if=\"summary.kind === 'mute'\" class=\"sg-summary fr-summary--mute\">\n <span class=\"sg-summary__icon\"><Icon :d=\"ICON.info\" :size=\"16\" /></span>\n <span>Turn on at least one field to form groups.</span>\n </div>\n <div v-else-if=\"summary.kind === 'warn'\" class=\"sg-summary sg-summary--warn\">\n <span class=\"sg-summary__icon\"><Icon :d=\"ICON.warn\" :size=\"16\" /></span>\n <span>\n Including <b>{{ repOnField?.key }}</b> makes every sample unique —\n <b>{{ baseGroups.length }} groups of 1</b>. Switch it off to keep replicates together.\n </span>\n </div>\n <div v-else-if=\"summary.kind === 'singletons'\" class=\"sg-summary sg-summary--warn\">\n <span class=\"sg-summary__icon\"><Icon :d=\"ICON.warn\" :size=\"16\" /></span>\n <span>\n <b>{{ counts.filter(count => count === 1).length }} of {{ baseGroups.length }} groups</b> have one sample.\n Switch off a field to form larger groups.\n </span>\n </div>\n <div v-else class=\"sg-summary\">\n <span class=\"sg-summary__icon\"><Icon :d=\"ICON.info\" :size=\"16\" /></span>\n <span>\n Grouping by <b>{{ activeFactors.join(' × ') }}</b> →\n <b>{{ baseGroups.length }} groups<template v-if=\"uniform\"> of {{ counts[0] }}</template></b>.\n </span>\n </div>\n </template>\n </div>\n\n <!-- rail -->\n <div class=\"sg-body__side\">\n <div class=\"sg-preview__head\">\n Resulting groups\n <span class=\"sg-preview__count\">{{ groups.length }} groups · {{ groupedSampleTotal }} samples</span>\n </div>\n <div class=\"sg-grouplist\">\n <div v-if=\"groups.length === 0\" class=\"fr-empty\">No groups yet — pick a field to start.</div>\n <div v-for=\"group in groups\" v-else :key=\"group.name\" class=\"sg-group\">\n <span class=\"sg-dot\" :style=\"group.name === QC_POOL_NAME ? QC_DOT_STYLE : { background: group.color }\" />\n <span class=\"sg-group__name\">{{ group.name }}</span>\n <span\n class=\"sg-group__bar\"\n :style=\"{ width: (MAX_BAR * group.count) / maxCount + 'px', '--g': group.color }\"\n />\n <span class=\"sg-group__count\">{{ group.count }}</span>\n </div>\n </div>\n\n <div class=\"sg-qc\">\n <div class=\"sg-qc__head\">\n <span class=\"sg-qc__title\">Quality control</span>\n <SegmentedControl\n class=\"fr-qc__route\"\n size=\"sm\"\n variant=\"card\"\n aria-label=\"Quality control routing\"\n :model-value=\"currentRoute\"\n :options=\"QC_ROUTE_OPTIONS\"\n @update:model-value=\"onRouteChange($event as QcRoute)\"\n />\n </div>\n <div class=\"fr-qc__chips\">\n <span\n v-for=\"chip in qcChips\"\n :key=\"chip.name\"\n class=\"sg-qc__chip\"\n :class=\"{ 'fr-qc__chip--off': currentRoute === 'Exclude' }\"\n >\n <span class=\"sg-dot\" :style=\"QC_DOT_STYLE\" />{{ chip.name }} ×{{ chip.count }}\n </span>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"sg-foot\">\n <span class=\"sg-foot__hint\">Applies to {{ sampleTotal }} samples{{ footHint }}</span>\n <div class=\"sg-foot__spacer\" />\n <BaseButton variant=\"ghost\" size=\"sm\" @click=\"emit('cancel')\">Cancel</BaseButton>\n <BaseButton variant=\"primary\" size=\"sm\" :disabled=\"activeFactors.length === 0\" @click=\"onApply\">\n <template #icon-left><Icon :d=\"ICON.check\" :size=\"15\" /></template>\n Apply grouping\n </BaseButton>\n </div>\n </div>\n</template>\n","/**\n * Pure cohort logic for SmartGroupManual (the \"manual\" grouping mode).\n *\n * Mirrors the design prototype's selection-first model: a flat list of samples,\n * each optionally assigned to a group / subgroup / sub-subgroup, derived into a\n * three-level cohort tree for the right rail. Framework-free for unit testing.\n */\n\n/** A sample with its (optional) manual cohort assignment. */\nexport interface ManualSample {\n name: string\n group: string | null\n sub: string | null\n sub2: string | null\n color: string | null\n}\n\n/** Which slice of the sample list is shown. */\nexport type ManualFilter = 'all' | 'unassigned' | 'assigned'\n\n/** A sample paired with its index in the source list (so the UI can toggle it). */\nexport interface ManualMatch {\n sample: ManualSample\n index: number\n}\n\n/** Second-level subgroup node: a sub-subgroup name → count map. */\nexport interface CohortSubNode {\n total: number\n subs2: Map<string, number>\n}\n\n/** Top-level cohort node: colour, total, and its subgroups. */\nexport interface CohortNode {\n color: string | null\n total: number\n subs: Map<string, CohortSubNode>\n}\n\n/** The assignment values applied to a selection. */\nexport interface CohortAssignment {\n grp: string\n sub: string\n sub2: string\n color: string\n}\n\n/**\n * Build the group → subgroup → sub-subgroup tree from assigned samples.\n * Unassigned samples are ignored. Missing sub/sub2 values collapse to `—`.\n */\nexport function buildCohortTree(samples: ManualSample[]): Map<string, CohortNode> {\n const groups = new Map<string, CohortNode>()\n for (const sample of samples) {\n if (!sample.group) continue\n if (!groups.has(sample.group)) {\n groups.set(sample.group, { color: sample.color, total: 0, subs: new Map() })\n }\n const node = groups.get(sample.group)!\n node.total++\n\n const subKey = sample.sub || '—'\n if (!node.subs.has(subKey)) {\n node.subs.set(subKey, { total: 0, subs2: new Map() })\n }\n const subNode = node.subs.get(subKey)!\n subNode.total++\n\n const sub2Key = sample.sub2 || '—'\n subNode.subs2.set(sub2Key, (subNode.subs2.get(sub2Key) ?? 0) + 1)\n }\n return groups\n}\n\n/** Filter the sample list by a case-insensitive name query and assignment state. */\nexport function filterSamples(\n samples: ManualSample[],\n query: string,\n filter: ManualFilter,\n): ManualMatch[] {\n const q = query.trim().toLowerCase()\n return samples\n .map((sample, index) => ({ sample, index }))\n .filter(({ sample }) => {\n if (q && !sample.name.toLowerCase().includes(q)) return false\n if (filter === 'unassigned') return !sample.group\n if (filter === 'assigned') return !!sample.group\n return true\n })\n}\n\n/**\n * Apply a cohort assignment to the selected sample indices, returning a new list.\n * Blank subgroup / sub-subgroup values become `null`.\n */\nexport function assignSamples(\n samples: ManualSample[],\n selected: ReadonlySet<number>,\n assignment: CohortAssignment,\n): ManualSample[] {\n return samples.map((sample, index) =>\n selected.has(index)\n ? {\n ...sample,\n group: assignment.grp.trim(),\n sub: assignment.sub.trim() || null,\n sub2: assignment.sub2.trim() || null,\n color: assignment.color,\n }\n : sample,\n )\n}\n","<script lang=\"ts\">\n/**\n * SmartGroupManual — the \"manual\" mode of the Smart Group modal.\n *\n * Selection-first grouping for when sample names can't be parsed automatically:\n * a wide searchable/filterable sample list on the left, a persistent Cohorts\n * rail with a progress meter and group → subgroup → sub-subgroup tree on the\n * right, and a contextual assignment bar that slides up only once samples are\n * selected. Cohort derivation lives in the tested `./SmartGroupManual.cohorts`\n * module.\n *\n * The reference-fixture defaults live in this module-scope `<script>` block\n * because `defineProps` default factories are hoisted and cannot reference\n * bindings declared inside `<script setup>`.\n */\nimport type { ManualPaletteEntry, SmartGroupSeed } from './SmartGroup.types'\n\nconst DEFAULT_NAMES = [\n 'Pt001_TumorA_d7_rep1',\n 'Pt001_TumorA_d7_rep1_reseq2024',\n 'Pt001_TumorA_d14_rep1',\n 'Pt002_TumorB_d7_rep1',\n 'Pt002_tumorb_d7_rep2',\n 'Pt002_TumorB_d14_rep1',\n 'Pt003_NoID_d7_rep1',\n 'Pt003_TumorC_d14_rep1_redo',\n 'Ctrl_pooled_old_naming',\n 'control_2_(redo)',\n 'Pt004_TumorA_d7_2024batch2',\n 'Pt004_TumorA_d14',\n 'QC_pool_01',\n 'blank_run3',\n]\nconst DEFAULT_SEED: Record<string, SmartGroupSeed> = {\n Pt001_TumorA_d7_rep1: { group: 'Responder', sub: 'Day 7', sub2: 'Batch 1', color: 'var(--mint-sample-2)' },\n Pt001_TumorA_d14_rep1: { group: 'Responder', sub: 'Day 14', color: 'var(--mint-sample-2)' },\n Pt002_TumorB_d7_rep1: { group: 'Non-responder', sub: 'Day 7', sub2: 'Batch 2', color: 'var(--mint-sample-3)' },\n}\nconst DEFAULT_PALETTE: ManualPaletteEntry[] = [\n { c: 'var(--mint-sample-1)', k: 'blue' },\n { c: 'var(--mint-sample-2)', k: 'green' },\n { c: 'var(--mint-sample-3)', k: 'red' },\n { c: 'var(--mint-sample-4)', k: 'amber' },\n { c: 'var(--mint-sample-5)', k: 'violet' },\n { c: 'var(--mint-sample-6)', k: 'orange' },\n { c: 'var(--mint-sample-7)', k: 'cyan' },\n { c: 'var(--mint-sample-8)', k: 'teal' },\n]\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, h, ref, type FunctionalComponent } from 'vue'\nimport {\n assignSamples,\n buildCohortTree,\n filterSamples,\n type ManualFilter,\n type ManualSample,\n} from './SmartGroupManual.cohorts'\nimport BaseButton from './BaseButton.vue'\nimport type { ManualDonePayload, SmartGroupManualProps } from './SmartGroup.types'\n\nconst props = withDefaults(defineProps<SmartGroupManualProps>(), {\n sampleNames: () => DEFAULT_NAMES,\n seed: () => DEFAULT_SEED,\n palette: () => DEFAULT_PALETTE,\n})\n\nconst emit = defineEmits<{\n done: [payload: ManualDonePayload]\n cancel: []\n auto: []\n close: []\n}>()\n\nconst Icon: FunctionalComponent<{ d: string; size?: number }> = iconProps =>\n h(\n 'svg',\n {\n width: iconProps.size ?? 16,\n height: iconProps.size ?? 16,\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n 'stroke-width': 2,\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n },\n [h('path', { d: iconProps.d })],\n )\nconst ICON = {\n search: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z',\n close: 'M6 18L18 6M6 6l12 12',\n chev: 'M6 9l6 6 6-6',\n layers: 'M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5',\n arrow: 'M5 12h14M13 6l6 6-6 6',\n check: 'M20 6L9 17l-5-5',\n}\n\n// Unique datalist ids so multiple instances on a page don't collide.\nlet mg2Uid = 0\nconst uid = ++mg2Uid\nconst groupListId = `sg-mg2-groups-${uid}`\nconst subListId = `sg-mg2-subs-${uid}`\nconst sub2ListId = `sg-mg2-subs2-${uid}`\n\nfunction buildInitial(): ManualSample[] {\n return props.sampleNames.map(name => {\n const seeded = props.seed[name]\n return {\n name,\n group: seeded?.group ?? null,\n sub: seeded?.sub ?? null,\n sub2: seeded?.sub2 ?? null,\n color: seeded?.color ?? null,\n }\n })\n}\n\nconst samples = ref<ManualSample[]>(buildInitial())\nconst query = ref('')\nconst filter = ref<ManualFilter>('all')\nconst sel = ref<Set<number>>(new Set())\nconst grp = ref('')\nconst sub = ref('')\nconst sub2 = ref('')\nconst color = ref(props.palette[0]?.c ?? 'var(--mint-sample-1)')\n\nconst assignedCount = computed(() => samples.value.filter(sample => sample.group).length)\nconst unassignedCount = computed(() => samples.value.length - assignedCount.value)\nconst matches = computed(() => filterSamples(samples.value, query.value, filter.value))\nconst matchIdx = computed(() => matches.value.map(match => match.index))\nconst allMatchSelected = computed(\n () => matchIdx.value.length > 0 && matchIdx.value.every(index => sel.value.has(index)),\n)\nconst tree = computed(() => buildCohortTree(samples.value))\n\nconst existingGroups = computed(() => {\n const map = new Map<string, string | null>()\n samples.value.forEach(sample => {\n if (sample.group && !map.has(sample.group)) map.set(sample.group, sample.color)\n })\n return map\n})\nconst groupKeys = computed(() => [...existingGroups.value.keys()])\nconst existingSubs = computed(() => [...new Set(samples.value.map(sample => sample.sub).filter(Boolean))] as string[])\nconst existingSub2 = computed(() => [...new Set(samples.value.map(sample => sample.sub2).filter(Boolean))] as string[])\n\nconst canAssign = computed(() => sel.value.size > 0 && grp.value.trim().length > 0)\nconst pct = computed(() =>\n samples.value.length ? Math.round((assignedCount.value / samples.value.length) * 100) : 0,\n)\nconst chips = computed<{ k: ManualFilter; label: string; n: number }[]>(() => [\n { k: 'all', label: 'All', n: samples.value.length },\n { k: 'unassigned', label: 'Unassigned', n: unassignedCount.value },\n { k: 'assigned', label: 'Assigned', n: assignedCount.value },\n])\n\nfunction subsWithout(node: { subs2: Map<string, number> }) {\n return [...node.subs2.entries()].filter(([key]) => key !== '—')\n}\n\nfunction toggle(index: number) {\n const next = new Set(sel.value)\n next.has(index) ? next.delete(index) : next.add(index)\n sel.value = next\n}\nfunction toggleAll() {\n const next = new Set(sel.value)\n if (allMatchSelected.value) matchIdx.value.forEach(index => next.delete(index))\n else matchIdx.value.forEach(index => next.add(index))\n sel.value = next\n}\nfunction onGroupInput(value: string) {\n grp.value = value\n const seeded = existingGroups.value.get(value)\n if (seeded) {\n // Existing cohort → adopt its colour.\n color.value = seeded\n return\n }\n // New cohort name → pick the first palette swatch not already in use, so\n // editing a matched name into a new one doesn't bleed the previous colour.\n const used = new Set(existingGroups.value.values())\n color.value = props.palette.find(p => !used.has(p.c))?.c ?? props.palette[0]?.c ?? 'var(--mint-sample-1)'\n}\nfunction assign() {\n if (sel.value.size === 0 || !grp.value.trim()) return\n samples.value = assignSamples(samples.value, sel.value, {\n grp: grp.value,\n sub: sub.value,\n sub2: sub2.value,\n color: color.value,\n })\n sel.value = new Set()\n grp.value = ''\n sub.value = ''\n sub2.value = ''\n}\nfunction clearSel() {\n sel.value = new Set()\n}\nfunction onDone() {\n emit('done', { samples: samples.value })\n}\n</script>\n\n<template>\n <div class=\"sg-modal mg2\">\n <!-- header -->\n <div class=\"sg-head\">\n <Icon :d=\"ICON.layers\" :size=\"18\" />\n <span class=\"sg-head__title\">Group samples</span>\n <span class=\"sg-head__sub\">· when names can’t be parsed automatically</span>\n <div class=\"sg-head__spacer\" />\n <div class=\"sg-mode\" role=\"tablist\" aria-label=\"Grouping mode\">\n <button type=\"button\" class=\"sg-mode__btn\" role=\"tab\" :aria-selected=\"false\" @click=\"emit('auto')\">Auto</button>\n <button type=\"button\" class=\"sg-mode__btn sg-mode__btn--on\" role=\"tab\" :aria-selected=\"true\">Manual</button>\n </div>\n <button class=\"sg-head__close\" aria-label=\"Close\" @click=\"emit('close')\">\n <Icon :d=\"ICON.close\" :size=\"16\" />\n </button>\n </div>\n\n <div class=\"mg2-body\">\n <!-- LEFT — find & select -->\n <div class=\"mg2-find\">\n <div class=\"mg2-search\">\n <Icon :d=\"ICON.search\" :size=\"15\" />\n <input v-model=\"query\" placeholder=\"Search names — e.g. TumorA, d7, Pt002…\" />\n <button v-if=\"query\" class=\"mg2-search__clr\" aria-label=\"Clear\" @click=\"query = ''\">\n <Icon :d=\"ICON.close\" :size=\"13\" />\n </button>\n </div>\n\n <div class=\"mg2-chips\">\n <button\n v-for=\"chip in chips\"\n :key=\"chip.k\"\n class=\"mg2-chip\"\n :class=\"{ 'mg2-chip--on': filter === chip.k }\"\n @click=\"filter = chip.k\"\n >\n {{ chip.label }}<span class=\"mg2-chip__n\">{{ chip.n }}</span>\n </button>\n </div>\n\n <div class=\"mg2-selbar\">\n <label class=\"mg2-check\">\n <input type=\"checkbox\" :checked=\"allMatchSelected\" @change=\"toggleAll\" />\n <span>Select all<template v-if=\"filter !== 'all'\"> {{ filter }}</template></span>\n </label>\n <span class=\"mg2-selbar__n\">{{ matchIdx.length }} shown</span>\n </div>\n\n <div class=\"mg2-list\">\n <div v-if=\"matches.length === 0\" class=\"mg2-list__empty\">No samples match “{{ query }}”.</div>\n <div\n v-for=\"match in matches\"\n :key=\"match.index\"\n class=\"mg2-row\"\n :class=\"{ 'mg2-row--on': sel.has(match.index) }\"\n @click=\"toggle(match.index)\"\n >\n <input type=\"checkbox\" class=\"mg2-cb\" :checked=\"sel.has(match.index)\" readonly />\n <span class=\"mg2-row__name\">{{ match.sample.name }}</span>\n <span v-if=\"match.sample.group\" class=\"mg2-row__tag\">\n <span class=\"sg-dot\" :style=\"{ background: match.sample.color ?? undefined }\" />{{ match.sample.group\n }}<template v-if=\"match.sample.sub\"> · {{ match.sample.sub }}</template>\n </span>\n <span v-else class=\"mg2-row__tag mg2-row__tag--none\">unassigned</span>\n </div>\n </div>\n </div>\n\n <!-- RIGHT — cohorts (persistent output) -->\n <div class=\"mg2-cohorts\">\n <div class=\"mg2-cohorts__head\">\n <span class=\"mg2-cohorts__t\">Cohorts</span>\n <span class=\"mg2-cohorts__n\">{{ tree.size }} groups</span>\n </div>\n\n <div class=\"mg2-progress\">\n <div class=\"mg2-progress__top\">\n <span><b>{{ assignedCount }}</b> of {{ samples.length }} grouped</span>\n <span class=\"mg2-progress__pct\">{{ pct }}%</span>\n </div>\n <div class=\"mg2-progress__track\"><div class=\"mg2-progress__fill\" :style=\"{ width: pct + '%' }\" /></div>\n </div>\n\n <div class=\"mg2-tree\">\n <div v-if=\"tree.size === 0\" class=\"mg2-tree__empty\">\n No cohorts yet. Select samples and assign them to start building.\n </div>\n <div v-for=\"[name, group] in [...tree.entries()]\" :key=\"name\" class=\"mg2-grp\">\n <div class=\"mg2-grp__row\">\n <span class=\"sg-dot\" :style=\"{ background: group.color ?? undefined }\" />\n <span class=\"mg2-grp__name\">{{ name }}</span>\n <span class=\"mg2-grp__n\">{{ group.total }}</span>\n </div>\n <template v-for=\"[subName, subNode] in [...group.subs.entries()]\" :key=\"subName\">\n <div class=\"mg2-sub\">\n <span class=\"mg2-sub__name\">{{ subName }}</span>\n <span class=\"mg2-sub__n\">{{ subNode.total }}</span>\n </div>\n <div v-for=\"[s2, count] in subsWithout(subNode)\" :key=\"s2\" class=\"mg2-sub2\">\n <span class=\"mg2-sub2__name\">{{ s2 }}</span>\n <span class=\"mg2-sub2__n\">{{ count }}</span>\n </div>\n </template>\n </div>\n </div>\n\n <button\n v-if=\"unassignedCount > 0\"\n class=\"mg2-unassigned\"\n :class=\"{ 'mg2-unassigned--on': filter === 'unassigned' }\"\n @click=\"filter = filter === 'unassigned' ? 'all' : 'unassigned'\"\n >\n <span class=\"sg-dot\" :style=\"{ background: 'var(--mint-sample-9)' }\" />\n <b>{{ unassignedCount }}</b> still unassigned\n <span class=\"mg2-unassigned__act\">{{ filter === 'unassigned' ? 'showing' : 'show' }}</span>\n </button>\n </div>\n </div>\n\n <!-- contextual assignment bar -->\n <div class=\"mg2-bar\" :class=\"{ 'mg2-bar--open': sel.size > 0 }\">\n <div class=\"mg2-bar__inner\">\n <span class=\"mg2-bar__count\"><b>{{ sel.size }}</b> selected</span>\n <Icon :d=\"ICON.arrow\" :size=\"16\" />\n <div class=\"mg2-field mg2-field--grow\">\n <span class=\"mg2-field__l\">Group</span>\n <div class=\"mg2-combo\">\n <span class=\"sg-dot\" :style=\"{ background: color }\" />\n <input\n :list=\"groupListId\"\n :value=\"grp\"\n placeholder=\"Type or pick — e.g. Responder\"\n @input=\"onGroupInput(($event.target as HTMLInputElement).value)\"\n />\n <span class=\"mg2-combo__chev\"><Icon :d=\"ICON.chev\" :size=\"14\" /></span>\n <datalist :id=\"groupListId\">\n <option v-for=\"key in groupKeys\" :key=\"key\" :value=\"key\" />\n </datalist>\n </div>\n </div>\n <div class=\"mg2-field mg2-field--grow\">\n <span class=\"mg2-field__l\">Subgroup</span>\n <div class=\"mg2-combo\">\n <input :list=\"subListId\" v-model=\"sub\" placeholder=\"optional — e.g. Day 7\" />\n <datalist :id=\"subListId\">\n <option v-for=\"value in existingSubs\" :key=\"value\" :value=\"value\" />\n </datalist>\n </div>\n </div>\n <div class=\"mg2-field mg2-field--grow\">\n <span class=\"mg2-field__l\">Sub-subgroup</span>\n <div class=\"mg2-combo\">\n <input :list=\"sub2ListId\" v-model=\"sub2\" placeholder=\"optional — e.g. Batch 2\" />\n <datalist :id=\"sub2ListId\">\n <option v-for=\"value in existingSub2\" :key=\"value\" :value=\"value\" />\n </datalist>\n </div>\n </div>\n <div class=\"mg2-field\">\n <span class=\"mg2-field__l\">Color</span>\n <div class=\"mg2-swatches\">\n <button\n v-for=\"swatch in palette\"\n :key=\"swatch.k\"\n class=\"mg2-swatch\"\n :class=\"{ 'mg2-swatch--on': color === swatch.c }\"\n :style=\"{ background: swatch.c }\"\n :aria-label=\"swatch.k\"\n @click=\"color = swatch.c\"\n />\n </div>\n </div>\n <div class=\"mg2-bar__spacer\" />\n <button class=\"mg2-bar__clear\" @click=\"clearSel\">Clear</button>\n <button class=\"sg-btn sg-btn--primary\" :disabled=\"!canAssign\" @click=\"assign\">\n <Icon :d=\"ICON.check\" :size=\"15\" />\n {{ canAssign ? `Assign ${sel.size} to “${grp.trim()}”` : 'Assign' }}\n </button>\n </div>\n </div>\n\n <!-- footer -->\n <div class=\"sg-foot\">\n <span class=\"sg-foot__hint\">{{ assignedCount }} of {{ samples.length }} samples grouped</span>\n <div class=\"sg-foot__spacer\" />\n <BaseButton variant=\"ghost\" size=\"sm\" @click=\"emit('cancel')\">Cancel</BaseButton>\n <BaseButton variant=\"primary\" size=\"sm\" @click=\"onDone\">Done</BaseButton>\n </div>\n </div>\n</template>\n","<script lang=\"ts\">\n/**\n * SmartGroupManual — the \"manual\" mode of the Smart Group modal.\n *\n * Selection-first grouping for when sample names can't be parsed automatically:\n * a wide searchable/filterable sample list on the left, a persistent Cohorts\n * rail with a progress meter and group → subgroup → sub-subgroup tree on the\n * right, and a contextual assignment bar that slides up only once samples are\n * selected. Cohort derivation lives in the tested `./SmartGroupManual.cohorts`\n * module.\n *\n * The reference-fixture defaults live in this module-scope `<script>` block\n * because `defineProps` default factories are hoisted and cannot reference\n * bindings declared inside `<script setup>`.\n */\nimport type { ManualPaletteEntry, SmartGroupSeed } from './SmartGroup.types'\n\nconst DEFAULT_NAMES = [\n 'Pt001_TumorA_d7_rep1',\n 'Pt001_TumorA_d7_rep1_reseq2024',\n 'Pt001_TumorA_d14_rep1',\n 'Pt002_TumorB_d7_rep1',\n 'Pt002_tumorb_d7_rep2',\n 'Pt002_TumorB_d14_rep1',\n 'Pt003_NoID_d7_rep1',\n 'Pt003_TumorC_d14_rep1_redo',\n 'Ctrl_pooled_old_naming',\n 'control_2_(redo)',\n 'Pt004_TumorA_d7_2024batch2',\n 'Pt004_TumorA_d14',\n 'QC_pool_01',\n 'blank_run3',\n]\nconst DEFAULT_SEED: Record<string, SmartGroupSeed> = {\n Pt001_TumorA_d7_rep1: { group: 'Responder', sub: 'Day 7', sub2: 'Batch 1', color: 'var(--mint-sample-2)' },\n Pt001_TumorA_d14_rep1: { group: 'Responder', sub: 'Day 14', color: 'var(--mint-sample-2)' },\n Pt002_TumorB_d7_rep1: { group: 'Non-responder', sub: 'Day 7', sub2: 'Batch 2', color: 'var(--mint-sample-3)' },\n}\nconst DEFAULT_PALETTE: ManualPaletteEntry[] = [\n { c: 'var(--mint-sample-1)', k: 'blue' },\n { c: 'var(--mint-sample-2)', k: 'green' },\n { c: 'var(--mint-sample-3)', k: 'red' },\n { c: 'var(--mint-sample-4)', k: 'amber' },\n { c: 'var(--mint-sample-5)', k: 'violet' },\n { c: 'var(--mint-sample-6)', k: 'orange' },\n { c: 'var(--mint-sample-7)', k: 'cyan' },\n { c: 'var(--mint-sample-8)', k: 'teal' },\n]\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, h, ref, type FunctionalComponent } from 'vue'\nimport {\n assignSamples,\n buildCohortTree,\n filterSamples,\n type ManualFilter,\n type ManualSample,\n} from './SmartGroupManual.cohorts'\nimport BaseButton from './BaseButton.vue'\nimport type { ManualDonePayload, SmartGroupManualProps } from './SmartGroup.types'\n\nconst props = withDefaults(defineProps<SmartGroupManualProps>(), {\n sampleNames: () => DEFAULT_NAMES,\n seed: () => DEFAULT_SEED,\n palette: () => DEFAULT_PALETTE,\n})\n\nconst emit = defineEmits<{\n done: [payload: ManualDonePayload]\n cancel: []\n auto: []\n close: []\n}>()\n\nconst Icon: FunctionalComponent<{ d: string; size?: number }> = iconProps =>\n h(\n 'svg',\n {\n width: iconProps.size ?? 16,\n height: iconProps.size ?? 16,\n viewBox: '0 0 24 24',\n fill: 'none',\n stroke: 'currentColor',\n 'stroke-width': 2,\n 'stroke-linecap': 'round',\n 'stroke-linejoin': 'round',\n },\n [h('path', { d: iconProps.d })],\n )\nconst ICON = {\n search: 'M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z',\n close: 'M6 18L18 6M6 6l12 12',\n chev: 'M6 9l6 6 6-6',\n layers: 'M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5',\n arrow: 'M5 12h14M13 6l6 6-6 6',\n check: 'M20 6L9 17l-5-5',\n}\n\n// Unique datalist ids so multiple instances on a page don't collide.\nlet mg2Uid = 0\nconst uid = ++mg2Uid\nconst groupListId = `sg-mg2-groups-${uid}`\nconst subListId = `sg-mg2-subs-${uid}`\nconst sub2ListId = `sg-mg2-subs2-${uid}`\n\nfunction buildInitial(): ManualSample[] {\n return props.sampleNames.map(name => {\n const seeded = props.seed[name]\n return {\n name,\n group: seeded?.group ?? null,\n sub: seeded?.sub ?? null,\n sub2: seeded?.sub2 ?? null,\n color: seeded?.color ?? null,\n }\n })\n}\n\nconst samples = ref<ManualSample[]>(buildInitial())\nconst query = ref('')\nconst filter = ref<ManualFilter>('all')\nconst sel = ref<Set<number>>(new Set())\nconst grp = ref('')\nconst sub = ref('')\nconst sub2 = ref('')\nconst color = ref(props.palette[0]?.c ?? 'var(--mint-sample-1)')\n\nconst assignedCount = computed(() => samples.value.filter(sample => sample.group).length)\nconst unassignedCount = computed(() => samples.value.length - assignedCount.value)\nconst matches = computed(() => filterSamples(samples.value, query.value, filter.value))\nconst matchIdx = computed(() => matches.value.map(match => match.index))\nconst allMatchSelected = computed(\n () => matchIdx.value.length > 0 && matchIdx.value.every(index => sel.value.has(index)),\n)\nconst tree = computed(() => buildCohortTree(samples.value))\n\nconst existingGroups = computed(() => {\n const map = new Map<string, string | null>()\n samples.value.forEach(sample => {\n if (sample.group && !map.has(sample.group)) map.set(sample.group, sample.color)\n })\n return map\n})\nconst groupKeys = computed(() => [...existingGroups.value.keys()])\nconst existingSubs = computed(() => [...new Set(samples.value.map(sample => sample.sub).filter(Boolean))] as string[])\nconst existingSub2 = computed(() => [...new Set(samples.value.map(sample => sample.sub2).filter(Boolean))] as string[])\n\nconst canAssign = computed(() => sel.value.size > 0 && grp.value.trim().length > 0)\nconst pct = computed(() =>\n samples.value.length ? Math.round((assignedCount.value / samples.value.length) * 100) : 0,\n)\nconst chips = computed<{ k: ManualFilter; label: string; n: number }[]>(() => [\n { k: 'all', label: 'All', n: samples.value.length },\n { k: 'unassigned', label: 'Unassigned', n: unassignedCount.value },\n { k: 'assigned', label: 'Assigned', n: assignedCount.value },\n])\n\nfunction subsWithout(node: { subs2: Map<string, number> }) {\n return [...node.subs2.entries()].filter(([key]) => key !== '—')\n}\n\nfunction toggle(index: number) {\n const next = new Set(sel.value)\n next.has(index) ? next.delete(index) : next.add(index)\n sel.value = next\n}\nfunction toggleAll() {\n const next = new Set(sel.value)\n if (allMatchSelected.value) matchIdx.value.forEach(index => next.delete(index))\n else matchIdx.value.forEach(index => next.add(index))\n sel.value = next\n}\nfunction onGroupInput(value: string) {\n grp.value = value\n const seeded = existingGroups.value.get(value)\n if (seeded) {\n // Existing cohort → adopt its colour.\n color.value = seeded\n return\n }\n // New cohort name → pick the first palette swatch not already in use, so\n // editing a matched name into a new one doesn't bleed the previous colour.\n const used = new Set(existingGroups.value.values())\n color.value = props.palette.find(p => !used.has(p.c))?.c ?? props.palette[0]?.c ?? 'var(--mint-sample-1)'\n}\nfunction assign() {\n if (sel.value.size === 0 || !grp.value.trim()) return\n samples.value = assignSamples(samples.value, sel.value, {\n grp: grp.value,\n sub: sub.value,\n sub2: sub2.value,\n color: color.value,\n })\n sel.value = new Set()\n grp.value = ''\n sub.value = ''\n sub2.value = ''\n}\nfunction clearSel() {\n sel.value = new Set()\n}\nfunction onDone() {\n emit('done', { samples: samples.value })\n}\n</script>\n\n<template>\n <div class=\"sg-modal mg2\">\n <!-- header -->\n <div class=\"sg-head\">\n <Icon :d=\"ICON.layers\" :size=\"18\" />\n <span class=\"sg-head__title\">Group samples</span>\n <span class=\"sg-head__sub\">· when names can’t be parsed automatically</span>\n <div class=\"sg-head__spacer\" />\n <div class=\"sg-mode\" role=\"tablist\" aria-label=\"Grouping mode\">\n <button type=\"button\" class=\"sg-mode__btn\" role=\"tab\" :aria-selected=\"false\" @click=\"emit('auto')\">Auto</button>\n <button type=\"button\" class=\"sg-mode__btn sg-mode__btn--on\" role=\"tab\" :aria-selected=\"true\">Manual</button>\n </div>\n <button class=\"sg-head__close\" aria-label=\"Close\" @click=\"emit('close')\">\n <Icon :d=\"ICON.close\" :size=\"16\" />\n </button>\n </div>\n\n <div class=\"mg2-body\">\n <!-- LEFT — find & select -->\n <div class=\"mg2-find\">\n <div class=\"mg2-search\">\n <Icon :d=\"ICON.search\" :size=\"15\" />\n <input v-model=\"query\" placeholder=\"Search names — e.g. TumorA, d7, Pt002…\" />\n <button v-if=\"query\" class=\"mg2-search__clr\" aria-label=\"Clear\" @click=\"query = ''\">\n <Icon :d=\"ICON.close\" :size=\"13\" />\n </button>\n </div>\n\n <div class=\"mg2-chips\">\n <button\n v-for=\"chip in chips\"\n :key=\"chip.k\"\n class=\"mg2-chip\"\n :class=\"{ 'mg2-chip--on': filter === chip.k }\"\n @click=\"filter = chip.k\"\n >\n {{ chip.label }}<span class=\"mg2-chip__n\">{{ chip.n }}</span>\n </button>\n </div>\n\n <div class=\"mg2-selbar\">\n <label class=\"mg2-check\">\n <input type=\"checkbox\" :checked=\"allMatchSelected\" @change=\"toggleAll\" />\n <span>Select all<template v-if=\"filter !== 'all'\"> {{ filter }}</template></span>\n </label>\n <span class=\"mg2-selbar__n\">{{ matchIdx.length }} shown</span>\n </div>\n\n <div class=\"mg2-list\">\n <div v-if=\"matches.length === 0\" class=\"mg2-list__empty\">No samples match “{{ query }}”.</div>\n <div\n v-for=\"match in matches\"\n :key=\"match.index\"\n class=\"mg2-row\"\n :class=\"{ 'mg2-row--on': sel.has(match.index) }\"\n @click=\"toggle(match.index)\"\n >\n <input type=\"checkbox\" class=\"mg2-cb\" :checked=\"sel.has(match.index)\" readonly />\n <span class=\"mg2-row__name\">{{ match.sample.name }}</span>\n <span v-if=\"match.sample.group\" class=\"mg2-row__tag\">\n <span class=\"sg-dot\" :style=\"{ background: match.sample.color ?? undefined }\" />{{ match.sample.group\n }}<template v-if=\"match.sample.sub\"> · {{ match.sample.sub }}</template>\n </span>\n <span v-else class=\"mg2-row__tag mg2-row__tag--none\">unassigned</span>\n </div>\n </div>\n </div>\n\n <!-- RIGHT — cohorts (persistent output) -->\n <div class=\"mg2-cohorts\">\n <div class=\"mg2-cohorts__head\">\n <span class=\"mg2-cohorts__t\">Cohorts</span>\n <span class=\"mg2-cohorts__n\">{{ tree.size }} groups</span>\n </div>\n\n <div class=\"mg2-progress\">\n <div class=\"mg2-progress__top\">\n <span><b>{{ assignedCount }}</b> of {{ samples.length }} grouped</span>\n <span class=\"mg2-progress__pct\">{{ pct }}%</span>\n </div>\n <div class=\"mg2-progress__track\"><div class=\"mg2-progress__fill\" :style=\"{ width: pct + '%' }\" /></div>\n </div>\n\n <div class=\"mg2-tree\">\n <div v-if=\"tree.size === 0\" class=\"mg2-tree__empty\">\n No cohorts yet. Select samples and assign them to start building.\n </div>\n <div v-for=\"[name, group] in [...tree.entries()]\" :key=\"name\" class=\"mg2-grp\">\n <div class=\"mg2-grp__row\">\n <span class=\"sg-dot\" :style=\"{ background: group.color ?? undefined }\" />\n <span class=\"mg2-grp__name\">{{ name }}</span>\n <span class=\"mg2-grp__n\">{{ group.total }}</span>\n </div>\n <template v-for=\"[subName, subNode] in [...group.subs.entries()]\" :key=\"subName\">\n <div class=\"mg2-sub\">\n <span class=\"mg2-sub__name\">{{ subName }}</span>\n <span class=\"mg2-sub__n\">{{ subNode.total }}</span>\n </div>\n <div v-for=\"[s2, count] in subsWithout(subNode)\" :key=\"s2\" class=\"mg2-sub2\">\n <span class=\"mg2-sub2__name\">{{ s2 }}</span>\n <span class=\"mg2-sub2__n\">{{ count }}</span>\n </div>\n </template>\n </div>\n </div>\n\n <button\n v-if=\"unassignedCount > 0\"\n class=\"mg2-unassigned\"\n :class=\"{ 'mg2-unassigned--on': filter === 'unassigned' }\"\n @click=\"filter = filter === 'unassigned' ? 'all' : 'unassigned'\"\n >\n <span class=\"sg-dot\" :style=\"{ background: 'var(--mint-sample-9)' }\" />\n <b>{{ unassignedCount }}</b> still unassigned\n <span class=\"mg2-unassigned__act\">{{ filter === 'unassigned' ? 'showing' : 'show' }}</span>\n </button>\n </div>\n </div>\n\n <!-- contextual assignment bar -->\n <div class=\"mg2-bar\" :class=\"{ 'mg2-bar--open': sel.size > 0 }\">\n <div class=\"mg2-bar__inner\">\n <span class=\"mg2-bar__count\"><b>{{ sel.size }}</b> selected</span>\n <Icon :d=\"ICON.arrow\" :size=\"16\" />\n <div class=\"mg2-field mg2-field--grow\">\n <span class=\"mg2-field__l\">Group</span>\n <div class=\"mg2-combo\">\n <span class=\"sg-dot\" :style=\"{ background: color }\" />\n <input\n :list=\"groupListId\"\n :value=\"grp\"\n placeholder=\"Type or pick — e.g. Responder\"\n @input=\"onGroupInput(($event.target as HTMLInputElement).value)\"\n />\n <span class=\"mg2-combo__chev\"><Icon :d=\"ICON.chev\" :size=\"14\" /></span>\n <datalist :id=\"groupListId\">\n <option v-for=\"key in groupKeys\" :key=\"key\" :value=\"key\" />\n </datalist>\n </div>\n </div>\n <div class=\"mg2-field mg2-field--grow\">\n <span class=\"mg2-field__l\">Subgroup</span>\n <div class=\"mg2-combo\">\n <input :list=\"subListId\" v-model=\"sub\" placeholder=\"optional — e.g. Day 7\" />\n <datalist :id=\"subListId\">\n <option v-for=\"value in existingSubs\" :key=\"value\" :value=\"value\" />\n </datalist>\n </div>\n </div>\n <div class=\"mg2-field mg2-field--grow\">\n <span class=\"mg2-field__l\">Sub-subgroup</span>\n <div class=\"mg2-combo\">\n <input :list=\"sub2ListId\" v-model=\"sub2\" placeholder=\"optional — e.g. Batch 2\" />\n <datalist :id=\"sub2ListId\">\n <option v-for=\"value in existingSub2\" :key=\"value\" :value=\"value\" />\n </datalist>\n </div>\n </div>\n <div class=\"mg2-field\">\n <span class=\"mg2-field__l\">Color</span>\n <div class=\"mg2-swatches\">\n <button\n v-for=\"swatch in palette\"\n :key=\"swatch.k\"\n class=\"mg2-swatch\"\n :class=\"{ 'mg2-swatch--on': color === swatch.c }\"\n :style=\"{ background: swatch.c }\"\n :aria-label=\"swatch.k\"\n @click=\"color = swatch.c\"\n />\n </div>\n </div>\n <div class=\"mg2-bar__spacer\" />\n <button class=\"mg2-bar__clear\" @click=\"clearSel\">Clear</button>\n <button class=\"sg-btn sg-btn--primary\" :disabled=\"!canAssign\" @click=\"assign\">\n <Icon :d=\"ICON.check\" :size=\"15\" />\n {{ canAssign ? `Assign ${sel.size} to “${grp.trim()}”` : 'Assign' }}\n </button>\n </div>\n </div>\n\n <!-- footer -->\n <div class=\"sg-foot\">\n <span class=\"sg-foot__hint\">{{ assignedCount }} of {{ samples.length }} samples grouped</span>\n <div class=\"sg-foot__spacer\" />\n <BaseButton variant=\"ghost\" size=\"sm\" @click=\"emit('cancel')\">Cancel</BaseButton>\n <BaseButton variant=\"primary\" size=\"sm\" @click=\"onDone\">Done</BaseButton>\n </div>\n </div>\n</template>\n","import type { SampleClass } from '../../../types/auto-group'\n\nexport function classKey(\n c: Pick<SampleClass, 'kind'> & { subKind?: string; tokenLength?: number },\n): string {\n const base = c.subKind ? `${c.kind}:${c.subKind}` : c.kind\n return c.tokenLength != null ? `${base}#${c.tokenLength}` : base\n}\n","{\n \"$schema\": \"./vocab.schema.json\",\n \"_doc\": {\n \"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.\",\n \"patterns\": \"Regex source strings (no surrounding slashes). Compiled with the `i` flag at load time.\",\n \"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.\",\n \"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).\",\n \"ionizationModes\": \"Lowercase polarity tokens (pos / positive / neg / negative). Suffixed to the subKind so POS and NEG acquisitions get independent schemas.\",\n \"tissueParentTokens\": \"Tokens that flag the immediately-following token as the organ subKind (so `tissues / kidney` becomes Biological/Tissues, tagging both positions).\",\n \"replicateToken\": \"Regex source for a token that names a replicate: rep1, Rep2, replicate3, R1, T1 (technical replicate). A column whose values all match is a replicate column: it stays a visible field and is left out of the default group key. B<n> is not in the pattern: on the lab share B13 / S13 are label tokens.\"\n },\n\n \"patterns\": {\n \"blank\": \"^(blank|blk|solv?blank|matrix.?blank)$\",\n \"iqc\": \"^(iqc|qc|pqc|pooled.?qc)$\",\n \"eqc\": \"^eqc$\",\n \"standard\": \"^(standards?|stds?\\\\d*|istd|cal(?:ibrator)?\\\\d*|.+standard)$\",\n \"spikeIn\": \"spike.?in\",\n \"stock\": \"^stock$\"\n },\n\n \"standardAllow\": [\n \"ExtractionS\",\n \"IS\",\n \"internal.standard\"\n ],\n\n \"matrixVocab\": {\n \"plasma\": \"Plasma\",\n \"serum\": \"Serum\",\n \"tissue\": \"Tissue\",\n \"tissues\": \"Tissues\",\n \"kidney\": \"Kidney\",\n \"liver\": \"Liver\",\n \"tumor\": \"Tumor\",\n \"urine\": \"Urine\",\n \"brain\": \"Brain\",\n \"muscle\": \"Muscle\",\n \"adipose\": \"Adipose\",\n \"csf\": \"CSF\",\n \"feces\": \"Feces\",\n \"faeces\": \"Faeces\",\n \"cells\": \"Cells\",\n \"cell\": \"Cells\",\n \"media\": \"Media\",\n \"medium\": \"Medium\",\n \"extract\": \"Extract\"\n },\n\n \"ionizationModes\": {\n \"pos\": \"POS\",\n \"positive\": \"POS\",\n \"neg\": \"NEG\",\n \"negative\": \"NEG\"\n },\n\n \"tissueParentTokens\": [\"tissue\", \"tissues\"],\n\n \"replicateToken\": \"^(?:rep(?:licate)?|r|t)\\\\d+$\"\n}\n","import type { SampleClass, SampleClassKind, ClassDisposition } from '../../../types/auto-group'\nimport { classKey } from './classKey'\nimport vocabData from './vocab.json'\n\nexport interface ClassVocab {\n blankPattern: RegExp\n iqcPattern: RegExp\n eqcPattern: RegExp\n standardPattern: RegExp\n standardAllow: Set<string>\n spikeInPattern: RegExp\n stockPattern: RegExp\n /**\n * Matrix lookup: lowercase token → canonical display name. Matching is\n * case-insensitive, and several aliases can collapse to one display name\n * (cell → Cells, medium → Medium).\n */\n matrixVocab: Map<string, string>\n /** Lowercase polarity tokens → POS / NEG. */\n ionizationModes: Map<string, 'POS' | 'NEG'>\n /** Lowercase tokens that flag the next token as the organ subKind. */\n tissueParentTokens: Set<string>\n}\n\ntype VocabFile = typeof vocabData\ntype IonMode = 'POS' | 'NEG'\n\nfunction compileVocab(data: VocabFile): ClassVocab {\n return {\n blankPattern: new RegExp(data.patterns.blank, 'i'),\n iqcPattern: new RegExp(data.patterns.iqc, 'i'),\n eqcPattern: new RegExp(data.patterns.eqc, 'i'),\n standardPattern: new RegExp(data.patterns.standard, 'i'),\n spikeInPattern: new RegExp(data.patterns.spikeIn, 'i'),\n stockPattern: new RegExp(data.patterns.stock, 'i'),\n standardAllow: new Set(data.standardAllow),\n matrixVocab: new Map(Object.entries(data.matrixVocab)),\n ionizationModes: new Map(\n Object.entries(data.ionizationModes).map(([k, v]) => [k, v as IonMode]),\n ),\n tissueParentTokens: new Set(data.tissueParentTokens),\n }\n}\n\nexport const classVocab: ClassVocab = compileVocab(vocabData)\n\nfunction matrixCanonical(token: string, vocab: ClassVocab): string | undefined {\n return vocab.matrixVocab.get(token.toLowerCase())\n}\n\nfunction isTissueParentToken(token: string, vocab: ClassVocab): boolean {\n return vocab.tissueParentTokens.has(token.toLowerCase())\n}\n\nexport interface DetectClassOptions {\n vocab?: Partial<ClassVocab>\n /** Per-sample explicit `sample_type` hint (e.g. from experiment design data). */\n sampleTypeHints?: (string | undefined)[]\n /** Number of leading tokens that all samples share (`TokenizedBatch.sharedPrefix`). */\n sharedPrefix?: number\n}\n\n/** A token that names a QC-type class (blank, QC, standard, spike-in, stock). */\nexport function isClassMarker(token: string, vocab: ClassVocab = classVocab): boolean {\n return (\n vocab.blankPattern.test(token) || vocab.iqcPattern.test(token) || vocab.eqcPattern.test(token)\n || vocab.standardPattern.test(token) || vocab.standardAllow.has(token)\n || vocab.spikeInPattern.test(token) || vocab.stockPattern.test(token)\n )\n}\n\n/** Kinds that form experimental groups; all other kinds are QC-type overlay classes. */\nexport function isExperimentalKind(kind: SampleClassKind): boolean {\n return kind === 'biological' || kind === 'unknown'\n}\n\nconst DEFAULT_DISPOSITION: Record<SampleClassKind, ClassDisposition> = {\n biological: 'group',\n iqc: 'overlay',\n eqc: 'overlay',\n standard: 'overlay',\n 'spike-in': 'overlay',\n stock: 'overlay',\n blank: 'overlay',\n unknown: 'group',\n}\n\nfunction classifyTokens(\n tokens: string[],\n vocab: ClassVocab,\n qcFrom: number,\n): { kind: SampleClassKind; subKind?: string; tagPositions: number[] } {\n // First-match-wins ordering: Blank → IQC → EQC → Standard → SpikeIn → Stock → Biological → Unknown.\n // QC kinds are read from `qcFrom`: a token that all names share (the\n // experiment title) cannot mark one sample as QC.\n for (let i = qcFrom; i < tokens.length; i++) {\n if (vocab.blankPattern.test(tokens[i])) return { kind: 'blank', tagPositions: [i] }\n }\n for (let i = qcFrom; i < tokens.length; i++) {\n if (vocab.iqcPattern.test(tokens[i])) return { kind: 'iqc', tagPositions: [i] }\n }\n for (let i = qcFrom; i < tokens.length; i++) {\n if (vocab.eqcPattern.test(tokens[i])) return { kind: 'eqc', tagPositions: [i] }\n }\n for (let i = qcFrom; i < tokens.length; i++) {\n if (vocab.standardPattern.test(tokens[i]) || vocab.standardAllow.has(tokens[i])) {\n return { kind: 'standard', tagPositions: [i] }\n }\n }\n for (let i = qcFrom; i < tokens.length; i++) {\n if (vocab.spikeInPattern.test(tokens[i])) return { kind: 'spike-in', tagPositions: [i] }\n }\n for (let i = qcFrom; i < tokens.length; i++) {\n if (vocab.stockPattern.test(tokens[i])) return { kind: 'stock', tagPositions: [i] }\n }\n for (let i = 0; i < tokens.length; i++) {\n const canonical = matrixCanonical(tokens[i], vocab)\n if (canonical) {\n const isTissueParent = isTissueParentToken(tokens[i], vocab)\n const tagPositions = [i]\n if (isTissueParent) {\n // \"Tissues\"/\"Tissue\" is the parent group; tag the next token (organ name) as well.\n if (i + 1 < tokens.length) tagPositions.push(i + 1)\n return { kind: 'biological', subKind: 'Tissues', tagPositions }\n }\n // Non-parent matrix token: tag any immediately preceding \"Tissues\"/\"Tissue\" parent.\n if (i > 0 && isTissueParentToken(tokens[i - 1], vocab)) {\n tagPositions.unshift(i - 1)\n return { kind: 'biological', subKind: 'Tissues', tagPositions }\n }\n return { kind: 'biological', subKind: canonical, tagPositions }\n }\n }\n return { kind: 'unknown', tagPositions: [] }\n}\n\n/**\n * LC-MS batches run positive- and negative-ion-mode acquisitions separately,\n * and the two produce different feature tables that should never be grouped\n * together. When a POS / NEG / positive / negative token is present we suffix\n * it to the class label so the schema builder treats each mode as its own class.\n */\nfunction ionizationModeFor(\n tokens: string[],\n vocab: ClassVocab,\n): { mode: IonMode; position: number } | undefined {\n for (let i = 0; i < tokens.length; i++) {\n const mode = vocab.ionizationModes.get(tokens[i].toLowerCase())\n if (mode) return { mode, position: i }\n }\n return undefined\n}\n\nfunction labelFor(kind: SampleClassKind, subKind?: string): string {\n const base: Record<SampleClassKind, string> = {\n biological: 'Biological',\n iqc: 'IQC',\n eqc: 'EQC',\n standard: 'Standard',\n 'spike-in': 'SpikeIn',\n stock: 'Stock',\n blank: 'Blank',\n unknown: 'Unknown',\n }\n return subKind ? `${base[kind]} / ${subKind}` : base[kind]\n}\n\n/** Classify each tokenized sample into a SampleClass. */\nexport function detectClass(\n tokenizedSamples: string[][],\n options: DetectClassOptions,\n): SampleClass[] {\n const vocab = { ...classVocab, ...(options.vocab ?? {}) }\n const hints = options.sampleTypeHints ?? []\n const buckets = new Map<string, SampleClass>()\n\n for (let i = 0; i < tokenizedSamples.length; i++) {\n const hint = hints[i]?.toLowerCase()\n let kind: SampleClassKind\n let subKind: string | undefined\n let tagPositions: number[]\n\n if (hint === 'qc') {\n kind = 'iqc'\n tagPositions = []\n } else if (hint === 'blank') {\n kind = 'blank'\n tagPositions = []\n } else {\n const r = classifyTokens(tokenizedSamples[i], vocab, options.sharedPrefix ?? 0)\n kind = r.kind\n subKind = r.subKind\n tagPositions = r.tagPositions\n }\n\n // Split classes by ionization mode (POS / NEG) so the two acquisitions get\n // independent schemas even when they share a matrix and sample-type vocab.\n const mode = ionizationModeFor(tokenizedSamples[i], vocab)\n if (mode) {\n subKind = subKind ? `${subKind} / ${mode.mode}` : mode.mode\n if (!tagPositions.includes(mode.position)) tagPositions.push(mode.position)\n }\n\n const key = classKey({ kind, subKind })\n const bucket = buckets.get(key)\n if (bucket) {\n bucket.members.push(i)\n for (const p of tagPositions) {\n if (!bucket.classTagPositions.includes(p)) bucket.classTagPositions.push(p)\n }\n } else {\n buckets.set(key, {\n kind,\n subKind,\n label: labelFor(kind, subKind),\n members: [i],\n classTagPositions: [...tagPositions],\n disposition: DEFAULT_DISPOSITION[kind],\n })\n }\n }\n\n return [...buckets.values()]\n}\n","import { isClassMarker } from './classify'\n\nconst DELIMITER_CANDIDATES = ['_', '-', ' ', '.'] as const\nexport type Delimiter = (typeof DELIMITER_CANDIDATES)[number]\n\n/** Pick the delimiter whose split produces the most consistent field count across `lines`. */\nexport function pickPrimaryDelimiter(lines: string[]): Delimiter {\n if (lines.length === 0) return '_'\n\n let best: Delimiter = '_'\n let bestScore = -1\n\n for (const candidate of DELIMITER_CANDIDATES) {\n const counts = lines.map(l => l.split(candidate).length)\n const frequency = new Map<number, number>()\n for (const c of counts) frequency.set(c, (frequency.get(c) ?? 0) + 1)\n\n // Mode and its frequency\n let mode = 1\n let modeFreq = 0\n for (const [c, f] of frequency) {\n if (f > modeFreq || (f === modeFreq && c > mode)) {\n mode = c\n modeFreq = f\n }\n }\n\n // A delimiter that produces field count 1 didn't split anything — score 0.\n const score = mode > 1 ? modeFreq / lines.length : 0\n if (score > bestScore) {\n best = candidate\n bestScore = score\n }\n }\n\n return best\n}\n\n/** Split a single sample name on `delimiter`. Empty input returns `['']` so callers\n * always receive at least one token. */\nexport function splitMulti(name: string, delimiter: Delimiter): string[] {\n return name.split(delimiter)\n}\n\n/** The `exp<NNN>_<date>_<operator>` fields at the start of a sample name. */\nexport interface ExperimentHeader {\n code?: string\n /** ISO date. Names carry it as YYMMDD, YYYYMMDD or DDMMYYYY. */\n date?: string\n operator?: string\n}\n\nexport interface TokenizedBatch {\n delimiter: Delimiter\n /** One row of tokens for each input name, in input order. */\n rows: string[][]\n /** Header fields with one value in the batch. They are not in `rows`. */\n experiment: ExperimentHeader\n /**\n * Number of leading tokens that all rows share (normally the experiment\n * title). They cannot tell a QC sample from a study sample.\n */\n sharedPrefix: number\n}\n\nconst HEADER_FIELDS = ['code', 'date', 'operator'] as const\n// Share of the names with a header that must carry the same operator before it\n// leaves the rows. The rest allows for stray names such as `exp157_test_blank_1`.\nconst OPERATOR_AGREEMENT = 0.9\n// Outside this window a 6-digit token is read day-first: `091123` is 2023-11-09, not 2009.\nconst FIRST_PLAUSIBLE_YEAR = 2015\n\nfunction isoDate(year: number, month: number, day: number): string | undefined {\n const lastYear = new Date().getFullYear() + 1\n if (year < FIRST_PLAUSIBLE_YEAR || year > lastYear) return undefined\n // `Date` rolls an impossible day over to the next month; reject that.\n const date = new Date(Date.UTC(year, month - 1, day))\n if (date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) return undefined\n return date.toISOString().slice(0, 10)\n}\n\n/** Read a 6- or 8-digit date token; year-first is tried before day-first. */\nexport function parseDateToken(token: string): string | undefined {\n const n = (from: number, to: number): number => Number(token.slice(from, to))\n if (/^\\d{8}$/.test(token)) return isoDate(n(0, 4), n(4, 6), n(6, 8)) ?? isoDate(n(4, 8), n(2, 4), n(0, 2))\n if (/^\\d{6}$/.test(token)) return isoDate(2000 + n(0, 2), n(2, 4), n(4, 6)) ?? isoDate(2000 + n(4, 6), n(2, 4), n(0, 2))\n return undefined\n}\n\ntype HeaderField = (typeof HEADER_FIELDS)[number]\n\n/**\n * Read the experiment code, date and operator from the left of a name, in any\n * order. `position` holds the token index of each field. A name with no code\n * and no date has no header, so `WT_Ctrl_1` keeps `WT` as a sample token.\n */\nfunction readHeader(tokens: string[]): { fields: ExperimentHeader; position: Partial<Record<HeaderField, number>> } {\n const fields: ExperimentHeader = {}\n const position: Partial<Record<HeaderField, number>> = {}\n let i = 0\n if (tokens.length > 1 && /^exp\\d+$/i.test(tokens[0])) {\n fields.code = tokens[0].toLowerCase()\n position.code = i++\n }\n for (; i < tokens.length - 1; i++) {\n const date = fields.date ? undefined : parseDateToken(tokens[i])\n if (date) {\n fields.date = date\n position.date = i\n } else if (!fields.operator && /^[A-Z]{2,3}$/.test(tokens[i]) && !isClassMarker(tokens[i])) {\n fields.operator = tokens[i]\n position.operator = i\n } else {\n break\n }\n }\n if (!fields.code && !fields.date) return { fields: {}, position: {} }\n return { fields, position }\n}\n\nconst MS_FILE_EXTENSION = /\\.(raw|mzml|mzxml|mzdata|wiff2?|d|mgf|csv|tsv|txt)$/i\n\n/** Drop a mass-spec file extension from each name, and any other extension that all names share. */\nfunction stripExtensions(names: string[]): string[] {\n const bare = names.map(name => name.replace(MS_FILE_EXTENSION, ''))\n const ext = /\\.[A-Za-z][A-Za-z0-9]{0,4}$/.exec(bare[0] ?? '')?.[0]\n return ext && bare.every(name => name.endsWith(ext)) ? bare.map(name => name.slice(0, -ext.length)) : bare\n}\n\n/**\n * Sample names → token rows.\n *\n * 1. Trim; drop a file extension that all names share.\n * 2. Split on runs of the primary delimiter.\n * 3. Drop a trailing 14-digit acquisition stamp.\n * 4. Read the experiment header. A field moves to `experiment` when the batch\n * has one value for it. Any other field stays in the rows as written, so\n * `QC` or `WT` after the date in some names is not taken for operator\n * initials.\n * 5. Count the tokens that all names share after the header (the experiment\n * title), so a title word such as `std` cannot classify the batch as QC.\n */\nexport function tokenizeBatch(names: string[]): TokenizedBatch {\n const cleaned = stripExtensions(names.map(name => name.trim()))\n const delimiter = pickPrimaryDelimiter(cleaned)\n const parsed = cleaned.map(name => {\n const split = name.split(delimiter).filter(Boolean)\n if (split.length > 1 && /^\\d{14}$/.test(split[split.length - 1])) split.pop()\n const tokens = split.length > 0 ? split : [name]\n return { tokens, ...readHeader(tokens) }\n })\n\n const experiment: ExperimentHeader = {}\n for (const field of ['code', 'date'] as const) {\n const values = new Set(parsed.map(({ fields }) => fields[field]).filter(Boolean))\n if (values.size === 1) experiment[field] = [...values][0]\n }\n // `exp<NNN>` and a date are clear by their form. Operator initials are not:\n // `QC`, `WT` or `KO` look the same. The operator is the caps token that\n // (almost) all complete headers carry; any other caps token stays in its row.\n const dated = parsed.filter(({ fields }) => fields.date)\n const complete = dated.length > 0 ? dated : parsed.filter(({ fields }) => fields.code)\n const operatorCounts = new Map<string, number>()\n for (const { fields } of complete) {\n if (fields.operator) operatorCounts.set(fields.operator, (operatorCounts.get(fields.operator) ?? 0) + 1)\n }\n const [operator, count] = [...operatorCounts].sort((a, b) => b[1] - a[1])[0] ?? []\n if (operator && count >= complete.length * OPERATOR_AGREEMENT) experiment.operator = operator\n\n const rows = parsed.map(({ tokens, fields, position }) => {\n const moved = new Set(\n HEADER_FIELDS.filter(field => experiment[field] && fields[field] === experiment[field]).map(field => position[field]),\n )\n return tokens.filter((_, index) => !moved.has(index))\n })\n\n // A title exists only after a header; without one a shared first token can\n // be the QC marker of a QC-only paste.\n let sharedPrefix = 0\n if (rows.length > 1 && Object.keys(experiment).length > 0) {\n while (rows.every(row => row.length > sharedPrefix + 1 && row[sharedPrefix] === rows[0][sharedPrefix])) sharedPrefix++\n // When only numbers follow, the shared tokens are the sample description\n // (a QC-only batch: `QC_1`, `QC_2`), not a title.\n if (rows.every(row => row.slice(sharedPrefix).every(token => /^\\d+[A-Za-z]?$/.test(token)))) sharedPrefix = 0\n }\n\n return { delimiter, experiment, rows, sharedPrefix }\n}\n","import type {\n ColumnInfo,\n ColumnRole,\n ClassSchema,\n NumericParse,\n SampleClassKind,\n} from '../../../types/auto-group'\nimport vocabData from './vocab.json'\n\nconst NUMERIC_VALUE_UNIT = /^(\\d+(?:(?:dot|\\.)\\d+)?)([A-Za-z%/μ]+)$/\nconst RATIO = /^(\\d+)to(\\d+)$/\nconst RUN_ORDER_PATTERN = /^\\d{2,4}[A-Z]?$/\n// Explicit replicate-token vocabulary: rep1 / Rep2 / replicate1 / R1 / T1.\n// These tokens almost always identify a within-group replicate dimension that\n// users do NOT want in the default group key — e.g. ctrl/rep1, ctrl/rep2\n// should land in one Ctrl group with two replicates, not two singleton groups.\nconst REPLICATE_TOKEN_PATTERN = new RegExp(vocabData.replicateToken, 'i')\n/**\n * A replicate counter is a dense run of small numbers or letters: `1, 2, 3`,\n * `01, 02, 03` or `A, B, C`. A quarter of the run can be missing (a failed\n * sample). `1, 5, 10` is a dose and `1, 2, 6, 7` an animal number: too sparse.\n */\nfunction isCounterRun(values: string[]): boolean {\n if (!values.every(value => /^(\\d{1,2}|[A-Za-z])$/.test(value))) return false\n // One batch can count some replicates 1, 2, 3 and others a, b, c.\n const ranks = [...new Set(values.map(value => (/\\d/.test(value) ? Number(value) : value.toUpperCase().charCodeAt(0) - 64)))]\n const allowedGaps = Math.max(1, Math.floor(ranks.length / 4))\n return Math.min(...ranks) >= 1 && Math.max(...ranks) <= ranks.length + allowedGaps\n}\n\n// A stem with a counter attached: C1, C2, H1.\nconst STEM_COUNTER_PATTERN = /^([A-Za-z]+)(\\d{1,2})$/\n// Share of a column that must agree before the column is split or read as\n// one-value-for-each-sample. The rest allows for odd names and re-injections.\nconst COLUMN_AGREEMENT = 0.9\nconst ID_MIN_CLASS_SIZE = 4\n\n/** Roles the Field Recipe offers as grouping fields. `replicate` and `id` are off by default. */\nexport const GROUPING_FIELD_ROLES: ReadonlySet<ColumnRole | undefined> = new Set<ColumnRole>([\n 'factor', 'numeric', 'replicate', 'id',\n])\n\n// Real-world LC-MS batches often have 30+ distinct conditions (subject IDs,\n// calibration levels, treatment combinations) in a single class. The earlier\n// cutoff of 20 routed everything above it to `ignore`, leaving such columns\n// out of the default groupBy — meaning a single giant group with no useful\n// split. 50 covers the common cases without making singleton-heavy columns\n// (sample IDs, timestamps) into factors.\nconst DEFAULT_FACTOR_CARDINALITY_MAX = 50\n// Numerics with only a handful of distinct levels (e.g. `5min` / `10min`,\n// `1nM` / `5nM` / `25nM`) read as factor levels by default. Users opt into\n// numeric binning explicitly via the column popover when they want it.\nconst LOW_CARDINALITY_NUMERIC_AS_FACTOR_MAX = 5\n\nexport function parseNumeric(token: string): NumericParse | null {\n const ratio = RATIO.exec(token)\n if (ratio) {\n const num = Number(ratio[1])\n const den = Number(ratio[2])\n if (den === 0) return null\n return { value: num / den, unit: 'ratio', raw: token }\n }\n const m = NUMERIC_VALUE_UNIT.exec(token)\n if (!m) return null\n const valueStr = m[1].replace(/dot/g, '.')\n const value = Number(valueStr)\n if (Number.isNaN(value)) return null\n return { value, unit: m[2], raw: token }\n}\n\nexport interface InferRoleInput {\n values: string[]\n position: number\n totalPositions: number\n classSize: number\n /** The column ends a name or a split token, where replicate counters sit. Defaults to the last position. */\n tail?: boolean\n}\n\nexport function inferRole(input: InferRoleInput): ColumnRole {\n const { values, position, totalPositions, classSize } = input\n const tail = input.tail ?? position === totalPositions - 1\n const unique = new Set(values)\n const cardinality = unique.size\n\n if (cardinality === 1) return 'constant'\n\n // replicate: explicit names (rep1, R1, T1), or a small counter that repeats\n // at the end of a name (1, 2, 3, 1, 2, 3). Empty cells come from names that\n // a column split did not match.\n const present = values.filter(Boolean)\n if (present.every(v => REPLICATE_TOKEN_PATTERN.test(v))) return 'replicate'\n if (tail && cardinality * 2 <= values.length && isCounterRun(present)) return 'replicate'\n\n // run-order: trailing position, injection-like values and high cardinality.\n // Numbers repeat when one batch holds two experiments, so half is enough.\n if (position === totalPositions - 1) {\n const allRunLike = values.every(v => RUN_ORDER_PATTERN.test(v))\n if (allRunLike && cardinality >= classSize * 0.5) return 'run-order'\n }\n\n // numeric: every value parses as value+unit (or ratio).\n const allNumeric = values.every(v => parseNumeric(v) !== null)\n if (allNumeric) {\n // A column like 5min / 10min reads as a factor in practice — users want to\n // group on the timepoint, not bin it.\n if (cardinality <= LOW_CARDINALITY_NUMERIC_AS_FACTOR_MAX) return 'factor'\n return 'numeric'\n }\n\n // id: one value for (almost) each sample, (almost) all with a digit — an\n // animal or sample number (212, M10, C01). Grouping by it gives groups of\n // one, so it is off by default. Unique words (Ctrl, DrugA, DrugB) are\n // conditions of a design with no replicates and stay a factor.\n if (\n values.length >= ID_MIN_CLASS_SIZE\n && cardinality >= values.length * COLUMN_AGREEMENT\n && values.filter(v => /\\d/.test(v)).length >= values.length * COLUMN_AGREEMENT\n ) return 'id'\n\n if (cardinality <= DEFAULT_FACTOR_CARDINALITY_MAX) return 'factor'\n\n return 'ignore'\n}\n\n/**\n * How a column splits when most of its values agree: on `-` into equal parts\n * (`2-1` → `2`, `1`), or into stem + counter (`C1` → `C`, `1`) when two or more\n * stems repeat with different counters. `D1`/`D3` (one stem) and `B13`/`S13`\n * (one counter for each stem) stay whole.\n */\nfunction columnSplitter(values: string[]): ((value: string) => string[]) | undefined {\n const agree = (test: (value: string) => boolean): boolean =>\n values.filter(test).length >= values.length * COLUMN_AGREEMENT\n\n const partCounts = new Map<number, number>()\n for (const value of values) {\n const parts = value.split('-').length\n partCounts.set(parts, (partCounts.get(parts) ?? 0) + 1)\n }\n const parts = [...partCounts].sort((a, b) => b[1] - a[1])[0]?.[0] ?? 1\n if (parts > 1 && agree(value => value.split('-').length === parts)) {\n return value => {\n const pieces = value.split('-')\n return pieces.length === parts ? pieces : [value, ...Array<string>(parts - 1).fill('')]\n }\n }\n\n const countersByStem = new Map<string, Set<string>>()\n for (const value of values) {\n const match = STEM_COUNTER_PATTERN.exec(value)\n if (match) countersByStem.set(match[1], (countersByStem.get(match[1]) ?? new Set()).add(match[2]))\n }\n const repeated = [...countersByStem.values()].filter(counters => counters.size > 1).length\n if (countersByStem.size > 1 && repeated * 2 >= countersByStem.size && agree(value => STEM_COUNTER_PATTERN.test(value))) {\n return value => STEM_COUNTER_PATTERN.exec(value)?.slice(1) ?? [value, '']\n }\n return undefined\n}\n\nexport interface ColumnSplit {\n rows: string[][]\n /** `origin[newIndex]` = index of the source column before the split. */\n origin: number[]\n /** New indices that end a split token. */\n tail: number[]\n}\n\n/** Split the columns of one class (rows of equal length) that hold two fields in one token. */\nexport function splitColumns(rows: string[][]): ColumnSplit {\n const width = rows[0]?.length ?? 0\n const splitters = Array.from({ length: width }, (_, col) => columnSplitter(rows.map(row => row[col] ?? '')))\n const origin: number[] = []\n const tail: number[] = []\n splitters.forEach((splitter, col) => {\n const parts = splitter ? splitter(rows[0][col]).length : 1\n for (let part = 0; part < parts; part++) origin.push(col)\n if (parts > 1) tail.push(origin.length - 1)\n })\n return {\n rows: rows.map(row => row.flatMap((token, col) => splitters[col]?.(token) ?? [token])),\n origin,\n tail,\n }\n}\n\n/**\n * Auto-detect a friendlier column name from the column's values + role.\n * Returns null when nothing matches, so callers can fall back to \"Field N\".\n *\n * Heuristics are content-derived and deterministic. They cover the common\n * LC-MS / proteomics naming conventions; users can override anything via\n * the column popover (rename input).\n */\nfunction inferColumnName(\n values: string[],\n uniqueValues: string[],\n role: ColumnRole,\n): string | null {\n if (uniqueValues.length === 0) return null\n\n // Numeric+unit columns name themselves by the dominant unit category.\n // We tally all values so a mixed-unit column (`1uL`, `1nM`) picks the\n // majority rather than the first sample's unit.\n const allNumeric = (role === 'numeric' || role === 'factor') && values.every(v => parseNumeric(v) !== null)\n if (allNumeric) {\n const categoryFor = (u: string): string | null => {\n const ul = u.toLowerCase()\n if (['min', 'sec', 's', 'hr', 'h', 'd', 'day', 'days', 'wk', 'week', 'hour', 'hours'].includes(ul)) return 'Timepoint'\n if (['nm', 'um', 'mm', 'm', 'molar', 'mol'].includes(ul)) return 'Concentration'\n if (['mg', 'g', 'kg', 'ng', 'ug', 'pg'].includes(ul)) return 'Mass'\n if (['ml', 'l', 'ul', 'nl', 'pl'].includes(ul)) return 'Volume'\n if (ul === '%') return 'Percentage'\n if (['c', 'k', 'celsius', 'kelvin'].includes(ul)) return 'Temperature'\n return null\n }\n const tally = new Map<string, number>()\n for (const v of values) {\n const p = parseNumeric(v)\n const cat = p ? categoryFor(p.unit) : null\n if (cat) tally.set(cat, (tally.get(cat) ?? 0) + 1)\n }\n let best: { cat: string; count: number } | null = null\n for (const [cat, count] of tally) {\n if (!best || count > best.count) best = { cat, count }\n }\n if (best && best.count >= values.length / 2) return best.cat\n }\n\n // Date-like all-digits tokens (YYMMDD or YYYYMMDD).\n if (values.every(v => /^\\d{6}$/.test(v) || /^\\d{8}$/.test(v))) return 'Date'\n\n // Run-order column = the trailing injection / acquisition sequence number.\n if (role === 'run-order') return 'Injection #'\n\n // Polarity tokens (POS/NEG/positive/negative).\n if (values.every(v => /^(pos|neg|positive|negative)$/i.test(v))) return 'Polarity'\n\n // Common biological matrices — kept in sync with classify.ts matrixVocab.\n 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))) {\n return 'Matrix'\n }\n\n // Experiment / study identifiers (exp268, study42, expt7).\n if (values.every(v => /^(exp|expt|study)\\d+$/i.test(v))) return 'Experiment'\n\n // Operator / Username: a constant column of 2-3 uppercase letters is almost\n // always operator initials (LCK, JD, AB). A column with two or more values\n // (WT / KO) is a factor and gets no name here.\n if (uniqueValues.length === 1 && /^[A-Z]{2,3}$/.test(uniqueValues[0])) return 'Username'\n\n if (role === 'replicate') return 'Replicate'\n if (role === 'id') return 'Sample ID'\n\n return null\n}\n\nexport function buildClassSchema(\n tokenizedMembers: string[][],\n classKind: SampleClassKind,\n subKind: string | undefined,\n classTagPositions: number[],\n /** Positions that end a split token (from `splitColumns`). */\n tailPositions: number[] = [],\n): ClassSchema {\n const totalPositions = tokenizedMembers[0]?.length ?? 0\n const classSize = tokenizedMembers.length\n\n const columns: ColumnInfo[] = []\n const tagSet = new Set(classTagPositions)\n // Replicate counters sit at the end of a name: the last position, or the one\n // before the injection number.\n const tailSet = new Set([...tailPositions, totalPositions - 1])\n\n // Right to left, so the injection column is known before its neighbour.\n for (let pos = totalPositions - 1; pos >= 0; pos--) {\n const values = tokenizedMembers.map(t => t[pos] ?? '')\n const uniqueValues = [...new Set(values)]\n\n let role: ColumnRole\n if (tagSet.has(pos) && uniqueValues.length === 1) {\n // Only constant tag positions become class-tag; multi-valued positions (e.g. organ\n // type within a Tissues class) fall through to normal role inference so they can be\n // used as factor columns for grouping.\n role = 'class-tag'\n } else {\n role = inferRole({ values, position: pos, totalPositions, classSize, tail: tailSet.has(pos) })\n }\n // Injection numbers with no zero padding (7, 18, 20) read as an id column\n // at the last position; a counter can sit before either form.\n const lastPosition = pos === totalPositions - 1\n if (role === 'run-order' || (lastPosition && role === 'id')) tailSet.add(pos - 1)\n\n const numeric =\n role === 'numeric'\n ? {\n byValue: Object.fromEntries(\n uniqueValues\n .map(v => [v, parseNumeric(v)] as const)\n .filter((e): e is readonly [string, NumericParse] => e[1] !== null),\n ),\n }\n : undefined\n\n const inferredName = inferColumnName(values, uniqueValues, role)\n columns.unshift({\n index: pos,\n name: inferredName ?? `Field ${pos + 1}`,\n sourceIndices: [pos],\n uniqueValues,\n cardinality: uniqueValues.length,\n role,\n numeric,\n // Legacy field\n type: pos === 0 ? 'prefix' : 'suffix',\n })\n }\n\n // The name is the field's identity across classes, so it must be unique in\n // a schema: a second `Date` or `Replicate` becomes `Date 2`. Grouping fields\n // take their names first, so a card keeps `Matrix` and the constant class\n // tag beside it becomes `Matrix 2`.\n const nameCounts = new Map<string, number>()\n const isField = (c: ColumnInfo): boolean => GROUPING_FIELD_ROLES.has(c.role)\n for (const c of [...columns.filter(isField), ...columns.filter(c => !isField(c))]) {\n const count = (nameCounts.get(c.name) ?? 0) + 1\n nameCounts.set(c.name, count)\n if (count > 1) c.name = `${c.name} ${count}`\n }\n\n const groupBy = columns.filter(c => c.role === 'factor' || c.role === 'numeric').map(c => c.index)\n\n return { classKind, subKind, columns, groupBy }\n}\n","import type { ClassSchema, MergeSuggestion } from '../../../types/auto-group'\n\nconst THRESHOLD = 0.95\nconst MIN_CLASS_SIZE = 4\n\nfunction functionalDependency(rows: string[][], srcIdx: number, dstIdx: number): number {\n const map = new Map<string, Set<string>>()\n for (const row of rows) {\n const a = row[srcIdx] ?? ''\n const b = row[dstIdx] ?? ''\n const set = map.get(a)\n if (set) set.add(b)\n else map.set(a, new Set([b]))\n }\n if (map.size === 0) return 0\n let functional = 0\n for (const set of map.values()) if (set.size === 1) functional++\n return functional / map.size\n}\n\nexport function findMerges(schema: ClassSchema, rows: string[][]): MergeSuggestion[] {\n if (rows.length < MIN_CLASS_SIZE) return []\n\n const out: MergeSuggestion[] = []\n for (let i = 0; i < schema.columns.length - 1; i++) {\n const a = schema.columns[i]\n const b = schema.columns[i + 1]\n if (a.role !== 'factor' && b.role !== 'factor') continue\n\n const forward = functionalDependency(rows, a.index, b.index)\n const backward = functionalDependency(rows, b.index, a.index)\n if (forward < THRESHOLD || backward < THRESHOLD) continue\n\n const merged = new Set(rows.map(r => `${r[a.index] ?? ''}_${r[b.index] ?? ''}`))\n if (merged.size < 2) continue\n\n out.push({\n classKind: schema.classKind,\n subKind: schema.subKind,\n columnIndices: [a.index, b.index],\n proposedName: `Column ${a.index + 1}–${b.index + 1}`,\n confidence: Math.min(forward, backward),\n })\n }\n return out\n}\n","export const DEFAULT_COLORS = [\n '#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6',\n '#EC4899', '#06B6D4', '#84CC16', '#F97316', '#6366F1',\n]\n\nexport const QC_OVERLAY_COLOR = '#6B7280'\n","import type {\n AutoGroupResult,\n ClassSchema,\n ColumnInfo,\n MetadataRow,\n NumericBinning,\n SampleClass,\n} from '../../../types/auto-group'\nimport type { SampleGroup } from '../../../types/components'\nimport type { TreeNode } from '../../../types/componentLabTypes'\nimport { DEFAULT_COLORS, QC_OVERLAY_COLOR } from './colors'\nimport { classKey } from './classKey'\n\nexport interface ComposeInput {\n tokenizedSamples: string[][]\n sampleNames: string[]\n schemas: Record<string, ClassSchema>\n classes: SampleClass[]\n prefixClassLabels?: boolean\n missingGroupName?: string\n}\n\n/** Apply the operation pipeline to a single raw token value:\n * numeric-parse → alias → exclude → bin\n * Returns null if the row is excluded.\n */\nfunction applyOps(col: ColumnInfo, raw: string): string | null {\n let value = raw\n\n // alias (operates on raw source value)\n if (col.ops?.alias && raw in col.ops.alias) {\n value = col.ops.alias[raw]\n }\n\n // exclude (post-alias)\n if (col.ops?.exclude?.includes(raw) || col.ops?.exclude?.includes(value)) {\n return null\n }\n\n // bin (only when role === 'numeric' and binning.mode === 'binned')\n if (col.role === 'numeric' && col.binning?.mode === 'binned') {\n const parsed = col.numeric?.byValue[raw]\n if (parsed && col.binning.edges) {\n const bin = pickBin(parsed.value, col.binning)\n if (bin !== null) value = bin\n }\n }\n\n return value\n}\n\nfunction pickBin(value: number, b: NumericBinning): string | null {\n if (!b.edges || b.edges.length === 0) return null\n for (let i = 0; i < b.edges.length; i++) {\n if (value < b.edges[i]) {\n return b.labels?.[i] ?? `< ${b.edges[i]}`\n }\n }\n const last = b.edges[b.edges.length - 1]\n return b.labels?.[b.edges.length] ?? `> ${last}`\n}\n\nexport function composeGroups(input: ComposeInput): AutoGroupResult {\n const experimentalGroups: SampleGroup[] = []\n const qcGroups: SampleGroup[] = []\n const excludedSamples: string[] = []\n const metadata: MetadataRow[] = []\n let colorIdx = 0\n\n // Class-label prefixing exists ONLY to disambiguate group names when several\n // experimental classes could emit the same factor key (e.g. a Liver class and\n // a Kidney class both producing \"5min / High\"). With a single experimental\n // class there is nothing to disambiguate, so groups start directly at the\n // user's first layer and the sample selector's major-group slot is not\n // wasted on a constant root. When a prefix IS needed it should occupy ONE\n // hierarchy layer, so prefer the short distinguishing `subKind` (\"Liver\")\n // over the full label (\"Biological / Liver\") — falling back to the full\n // label when two classes share a subKind (e.g. token-length splits).\n const experimentalClasses = input.classes.filter(\n c => c.disposition === 'group' && input.schemas[classKey(c)],\n )\n const classPrefix = new Map<SampleClass, string>()\n if (input.prefixClassLabels !== false && experimentalClasses.length > 1) {\n const shortCounts = new Map<string, number>()\n for (const c of experimentalClasses) {\n const short = c.subKind ?? c.label\n shortCounts.set(short, (shortCounts.get(short) ?? 0) + 1)\n }\n for (const c of experimentalClasses) {\n const short = c.subKind ?? c.label\n classPrefix.set(c, (shortCounts.get(short) ?? 0) > 1 ? c.label : short)\n }\n }\n\n // Nested hierarchy (class → groupBy levels → sample leaves) built alongside\n // the flat groups. `treeIndex` lets repeated members reuse the same path node.\n // PATH_SEP is a control char so it never collides with a real token value.\n const PATH_SEP = String.fromCharCode(1)\n const groupTree: TreeNode[] = []\n const treeIndex = new Map<string, TreeNode>()\n let treeColorIdx = 0\n\n for (const cls of input.classes) {\n const schema = input.schemas[classKey(cls)]\n if (!schema) continue\n\n if (cls.disposition === 'exclude') {\n for (const m of cls.members) excludedSamples.push(input.sampleNames[m])\n continue\n }\n\n // Per-class group map. group key built from enabled (groupBy) columns,\n // joined with ' / ' for hierarchical display.\n const groupMap = new Map<string, { name: string; samples: string[] }>()\n const colByIdx = new Map(schema.columns.map(c => [c.index, c]))\n\n // Only experimental (group) classes form the nested tree; overlay/QC stay flat.\n const buildsTree = cls.disposition === 'group'\n const runCol = schema.columns.find(c => c.role === 'run-order')\n const classNodeId = classKey(cls)\n let classNode: TreeNode | null = null\n\n for (const m of cls.members) {\n const tokens = input.tokenizedSamples[m]\n const rowName = input.sampleNames[m]\n let excluded = false\n const fieldValues: Record<string, string> = {}\n\n for (const col of schema.columns) {\n const sourceValues = col.sourceIndices.map(idx => tokens[idx] ?? '')\n const raw = sourceValues.join('_') // merged columns join their source tokens\n const display = applyOps(col, raw)\n if (display === null) {\n excluded = true\n break\n }\n fieldValues[col.displayName ?? col.name] = display\n }\n\n if (excluded) {\n excludedSamples.push(rowName)\n continue\n }\n\n const keyParts: string[] =\n cls.disposition === 'group'\n ? schema.groupBy.map(idx => {\n const col = colByIdx.get(idx)!\n return fieldValues[col.displayName ?? col.name]\n })\n : [cls.label]\n\n // Prepend the class's short distinguishing label (see `classPrefix`\n // above) so group names from different classes don't collide when they\n // share a factor value (e.g. two tissue classes both emitting \"5min\").\n //\n // Only fires for disposition === 'group' classes (`classPrefix` holds no\n // other class): overlay/exclude classes already use cls.label AS their\n // single key part, so prefixing again would produce \"IQC / IQC\". A class\n // with one condition has no factor keyParts and is named by the prefix\n // alone (\"Brain\" next to \"Liver / Ctrl\"). The Unknown class opts out —\n // its label (\"Unknown\") adds no signal.\n const hasMissingGroupPart =\n input.missingGroupName &&\n cls.disposition === 'group' &&\n keyParts.some(part => part.trim().length === 0)\n const prefix = cls.kind !== 'unknown' ? classPrefix.get(cls) : undefined\n const groupKey = hasMissingGroupPart\n ? input.missingGroupName!\n : [...(prefix ? [prefix] : []), ...keyParts].join(' / ') || cls.label\n\n const bucket = groupMap.get(groupKey)\n if (bucket) {\n bucket.samples.push(rowName)\n } else {\n groupMap.set(groupKey, { name: groupKey, samples: [rowName] })\n }\n\n metadata.push({ sampleName: rowName, fields: fieldValues, group: groupKey })\n\n // Grow the nested tree: class root → one node per groupBy column → leaf.\n if (buildsTree) {\n if (!classNode) {\n classNode = {\n id: classNodeId,\n label: cls.label,\n badge: 0,\n children: [],\n metadata: { color: DEFAULT_COLORS[treeColorIdx++ % DEFAULT_COLORS.length] },\n }\n treeIndex.set(classNodeId, classNode)\n groupTree.push(classNode)\n }\n classNode.badge = (classNode.badge as number) + 1\n let cursor = classNode\n let pathId = classNodeId\n // Reuse the already-computed groupBy values (`keyParts`) so the tree\n // levels can never drift from the flat group key.\n for (const val of keyParts) {\n pathId += PATH_SEP + val\n let node = treeIndex.get(pathId)\n if (!node) {\n node = { id: pathId, label: val, badge: 0, children: [] }\n cursor.children!.push(node)\n treeIndex.set(pathId, node)\n }\n node.badge = (node.badge as number) + 1\n cursor = node\n }\n // Injection value goes through the column's op pipeline (alias/exclude)\n // via `fieldValues`, matching what every other surface shows.\n const injection = runCol ? fieldValues[runCol.displayName ?? runCol.name] : undefined\n const leaf: TreeNode = { id: pathId + PATH_SEP + rowName, label: rowName, type: 'sample' }\n if (injection) {\n leaf.badge = injection\n leaf.metadata = { injection }\n }\n cursor.children!.push(leaf)\n }\n }\n\n for (const { name, samples } of groupMap.values()) {\n const color =\n cls.disposition === 'overlay'\n ? QC_OVERLAY_COLOR\n : DEFAULT_COLORS[colorIdx++ % DEFAULT_COLORS.length]\n const g: SampleGroup = { name, color, samples }\n if (cls.disposition === 'overlay') qcGroups.push(g)\n else experimentalGroups.push(g)\n }\n }\n\n return {\n groups: [...experimentalGroups, ...qcGroups],\n experimentalGroups,\n qcGroups,\n metadata,\n excludedSamples,\n schemas: Object.values(input.schemas),\n groupTree,\n }\n}\n","import type { ClassSchema, SchemaFingerprint } from '../../../types/auto-group'\n\nexport function serializeFingerprint(schemas: ClassSchema[]): SchemaFingerprint {\n return {\n version: 1,\n classes: schemas.map(s => ({\n kind: s.classKind,\n subKind: s.subKind,\n tokenLength: s.tokenLength,\n columns: s.columns.map(c => ({\n name: c.displayName ?? c.name,\n role: c.role ?? 'factor',\n sourceIndices: c.sourceIndices,\n binning: c.binning,\n ops: c.ops,\n })),\n groupBy: s.groupBy,\n })),\n }\n}\n\nexport function restoreFingerprint(\n fp: SchemaFingerprint,\n current: ClassSchema[],\n): ClassSchema[] {\n return fp.classes.map(snap => {\n // Match on token length too when the snapshot carries one, so length-split\n // classes sharing a (kind, subKind) restore to the right schema. Snapshots\n // saved before token-length splitting have no `tokenLength`; for those we\n // match on (kind, subKind) and prefer the candidate with the same column\n // count, so an old single-class fingerprint lands on the compatible split\n // instead of throwing on the first (wrong-arity) hit.\n const candidates = current.filter(\n c =>\n c.classKind === snap.kind &&\n c.subKind === snap.subKind &&\n (snap.tokenLength == null || c.tokenLength == null || c.tokenLength === snap.tokenLength),\n )\n const target =\n candidates.find(c => c.columns.length === snap.columns.length) ?? candidates[0]\n if (!target) {\n throw new Error(\n `Fingerprint class not present in current input: ${snap.kind}${snap.subKind ? '/' + snap.subKind : ''}`,\n )\n }\n if (target.columns.length !== snap.columns.length) {\n throw new Error(\n `Saved schema expects ${snap.columns.length} columns, current data has ${target.columns.length} for class ${snap.kind}${snap.subKind ? '/' + snap.subKind : ''}. column count mismatch`,\n )\n }\n return {\n ...target,\n groupBy: snap.groupBy,\n columns: target.columns.map((col, i) => ({\n ...col,\n displayName: snap.columns[i].name,\n role: snap.columns[i].role,\n binning: snap.columns[i].binning,\n ops: snap.columns[i].ops,\n })),\n }\n })\n}\n","import type { ClassSchema, ColumnInfo, SampleClass } from '../../../types/auto-group'\nimport { classKey } from './classKey'\n\nexport interface TemplateOptions {\n mode: 'blank' | 'prefilled'\n format: 'csv' | 'tsv'\n /** For prefilled mode. Indexed parallel to `samples`. */\n tokenizedSamples?: string[][]\n /** Used to compute the filename; omitted → falls back to `sample_metadata_<date>`. */\n experimentCode?: string\n /** Override the blank-mode suggested headers. Defaults to ['group','class','replicate','notes']. */\n suggestedColumns?: string[]\n}\n\nconst DEFAULT_BLANK_HEADERS = ['group', 'class', 'replicate', 'notes']\n\nfunction rfc4180Quote(cell: string): string {\n if (/[\",\\n]/.test(cell)) {\n return `\"${cell.replace(/\"/g, '\"\"')}\"`\n }\n return cell\n}\n\nfunction joinRow(cells: string[], sep: string): string {\n return sep === ','\n ? cells.map(rfc4180Quote).join(',')\n : cells.join('\\t')\n}\n\nfunction today(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nexport function composeTemplate(\n samples: string[],\n schemas: ClassSchema[] | null,\n classes: SampleClass[] | null,\n options: TemplateOptions,\n): { content: string; filename: string } {\n const sep = options.format === 'tsv' ? '\\t' : ','\n const ext = options.format === 'tsv' ? 'tsv' : 'csv'\n\n const stem = options.experimentCode ? `${options.experimentCode}_metadata` : 'sample_metadata'\n const filename = `${stem}_${today()}.${ext}`\n\n if (options.mode === 'blank' || !schemas || !classes) {\n const headers = ['sample_name', ...(options.suggestedColumns ?? DEFAULT_BLANK_HEADERS)]\n const rows = samples.map(s => {\n const r = [s, ...Array(headers.length - 1).fill('')]\n return joinRow(r, sep)\n })\n return { content: [joinRow(headers, sep), ...rows].join('\\n'), filename }\n }\n\n // Prefilled mode: union of all class columns, wide layout.\n const tokenized = options.tokenizedSamples ?? []\n const allColumns: { schemaKey: string; colName: string }[] = []\n\n // Hoist: build lookup maps before the per-sample loop.\n const sampleToClass = new Map<number, SampleClass>()\n for (const cls of classes) for (const m of cls.members) sampleToClass.set(m, cls)\n\n const schemaByKey = new Map<string, ClassSchema>()\n for (const s of schemas) {\n const k = classKey({ kind: s.classKind, subKind: s.subKind, tokenLength: s.tokenLength })\n schemaByKey.set(k, s)\n }\n\n const colByNameByKey = new Map<string, Map<string, ColumnInfo>>()\n const groupBySetByKey = new Map<string, Set<number>>()\n for (const s of schemas) {\n const k = classKey({ kind: s.classKind, subKind: s.subKind, tokenLength: s.tokenLength })\n const m = new Map<string, ColumnInfo>()\n for (const c of s.columns) m.set(c.displayName ?? c.name, c)\n colByNameByKey.set(k, m)\n groupBySetByKey.set(k, new Set(s.groupBy))\n // Collect allColumns with dedup via Set\n const seenColNames = new Set<string>()\n for (const c of s.columns) {\n const name = c.displayName ?? c.name\n if (!seenColNames.has(name)) {\n seenColNames.add(name)\n if (!allColumns.some(x => x.colName === name)) {\n allColumns.push({ schemaKey: k, colName: name })\n }\n }\n }\n }\n\n const headers = ['sample_name', 'class', ...allColumns.map(c => c.colName), 'group', 'notes']\n\n const rows: string[] = []\n for (let i = 0; i < samples.length; i++) {\n const cls = sampleToClass.get(i)\n const classLabel = cls?.label ?? ''\n const tokens = tokenized[i] ?? []\n const cells: string[] = [samples[i], classLabel]\n\n if (cls) {\n const k = classKey(cls)\n const groupBySet = groupBySetByKey.get(k)\n const colByName: Map<string, ColumnInfo> | undefined = colByNameByKey.get(k)\n const keyParts: string[] = []\n for (const col of allColumns) {\n if (col.schemaKey === k && colByName) {\n const schemaCol = colByName.get(col.colName)\n const raw =\n schemaCol?.sourceIndices.map(idx => tokens[idx] ?? '').join('_') ?? ''\n cells.push(raw)\n if (schemaCol && groupBySet?.has(schemaCol.index)) keyParts.push(raw)\n } else {\n cells.push('')\n }\n }\n cells.push(keyParts.join(' / '))\n } else {\n for (const _ of allColumns) cells.push('')\n cells.push('')\n }\n\n cells.push('') // notes\n rows.push(joinRow(cells, sep))\n }\n\n return { content: [joinRow(headers, sep), ...rows].join('\\n'), filename }\n}\n","import type { SampleClass } from '../../../types/auto-group'\nimport { isExperimentalKind } from './classify'\n\n/**\n * Token-length pre-grouping.\n *\n * After type classification (`detectClass`), members of one class can still have\n * different token counts — e.g. a Plasma batch where some names carry an extra\n * descriptor field. Splitting each class so every member shares the same token\n * length is the \"group same token-length sample, then extract tokens inside the\n * group\" step: it guarantees `buildClassSchema` sees a rectangular token matrix,\n * so column index N means the same field for every sample in the group.\n *\n * Each output class carries `tokenLength`; `classKey` folds that into the schema\n * key so length-split classes never collide. The label only gains a `· N fields`\n * suffix when a (kind, subKind) genuinely spans more than one length, keeping the\n * common (uniform) case visually identical to before.\n */\nexport function splitByTokenLength(\n classes: SampleClass[],\n tokenized: string[][],\n): SampleClass[] {\n const out: SampleClass[] = []\n\n for (const cls of classes) {\n // QC-kind classes (QC, blank, standard) are one group named by the class\n // label and never group by column, so they need no rectangular matrix.\n if (!isExperimentalKind(cls.kind)) {\n out.push(cls)\n continue\n }\n\n // Bucket members by token count, preserving first-seen order of lengths.\n const byLength = new Map<number, number[]>()\n const lengthOrder: number[] = []\n for (const m of cls.members) {\n const len = tokenized[m]?.length ?? 0\n const bucket = byLength.get(len)\n if (bucket) {\n bucket.push(m)\n } else {\n byLength.set(len, [m])\n lengthOrder.push(len)\n }\n }\n\n const multipleLengths = lengthOrder.length > 1\n for (const len of lengthOrder) {\n out.push({\n ...cls,\n members: byLength.get(len)!,\n tokenLength: len,\n // Drop class-tag positions that fall outside this split's token range.\n classTagPositions: cls.classTagPositions.filter(p => p < len),\n label: multipleLengths ? `${cls.label} · ${len} fields` : cls.label,\n })\n }\n }\n\n return out\n}\n","import type { ParsedCsvData } from '../../../types/auto-group'\n\nfunction parseCSVLine(line: string, delimiter: string = ','): string[] {\n const result: string[] = []\n let current = ''\n let inQuotes = false\n for (let i = 0; i < line.length; i++) {\n const char = line[i]\n if (char === '\"') {\n if (inQuotes && line[i + 1] === '\"') {\n current += '\"'\n i++\n } else {\n inQuotes = !inQuotes\n }\n } else if (char === delimiter && !inQuotes) {\n result.push(current.trim())\n current = ''\n } else {\n current += char\n }\n }\n result.push(current.trim())\n return result\n}\n\nexport function parseCSV(text: string): ParsedCsvData {\n const lines = text.trim().split('\\n')\n if (lines.length < 2) throw new Error('CSV must have at least a header and one data row')\n const firstLine = lines[0]\n const csvDelimiter = firstLine.includes('\\t') ? '\\t' : ','\n const headers = parseCSVLine(firstLine, csvDelimiter)\n const rows: Record<string, string>[] = []\n for (let i = 1; i < lines.length; i++) {\n const values = parseCSVLine(lines[i], csvDelimiter)\n if (values.length !== headers.length) continue\n const row: Record<string, string> = {}\n headers.forEach((h, idx) => { row[h] = values[idx] })\n rows.push(row)\n }\n const sampleKeywords = ['sample', 'name', 'id', 'sample_name', 'samplename', 'file name', 'filename', 'file_name']\n const sampleColumn = headers.find(h => sampleKeywords.includes(h.toLowerCase())) ?? headers[0]\n return { columns: headers, rows, sampleColumn, delimiter: csvDelimiter }\n}\n","import { computed, ref } from 'vue'\nimport type {\n AutoGroupResult,\n ClassDisposition,\n ClassSchema,\n ColumnInfo,\n ColumnRole,\n InputMode,\n MergeSuggestion,\n NumericBinning,\n OutlierInfo,\n ParsedCsvData,\n SampleClass,\n SchemaFingerprint,\n ValueOps,\n} from '../../types/auto-group'\nimport {\n GROUPING_FIELD_ROLES,\n buildClassSchema,\n classKey,\n composeGroups,\n composeTemplate,\n detectClass,\n findMerges,\n isExperimentalKind,\n restoreFingerprint,\n serializeFingerprint,\n splitByTokenLength,\n splitColumns,\n tokenizeBatch,\n type Delimiter,\n type ExperimentHeader,\n type TemplateOptions,\n} from './autoGroup'\nimport { unwrapExperimentDesignData } from '../../composables/experimentDesignData'\n\nconst STRUCTURED_MISSING_GROUP = 'Outlier'\n\ntype ParsedExperimentCsvData = ParsedCsvData & { sampleTypeHints: (string | undefined)[] }\n\ninterface LoadExperimentDataOptions {\n samples?: readonly string[]\n}\n\n// Re-export `parseCSV` for callers that imported it from this file.\nexport { parseCSV } from './autoGroup/csv-shim'\n\n/**\n * Sample-grouping engine behind `AutoGroupModalInternal`. Private to `SampleSelector`.\n */\nexport function useAutoGroup() {\n const inputMode = ref<InputMode>('paste')\n const rawText = ref('')\n const csvData = ref<ParsedCsvData | null>(null)\n const sampleTypeHints = ref<(string | undefined)[]>([])\n\n const classes = ref<SampleClass[]>([])\n const schemas = ref<Record<string, ClassSchema>>({})\n const activeClassKey = ref<string>('')\n\n const samples = computed(() => {\n if (csvData.value) {\n return csvData.value.rows.map(r => r[csvData.value!.sampleColumn] ?? '')\n }\n return rawText.value.split('\\n').map(l => l.trim()).filter(l => l.length > 0)\n })\n\n const tokenized = ref<string[][]>([])\n const delimiter = ref<Delimiter>('_')\n // Experiment code / date / operator that all pasted names share. Empty for\n // CSV and experiment input.\n const experiment = ref<ExperimentHeader>({})\n\n function parseInput() {\n if ((inputMode.value === 'csv' || inputMode.value === 'experiment') && csvData.value) {\n parseInputFromCsv()\n return\n }\n parseInputFromPaste()\n }\n\n function parseInputFromCsv() {\n experiment.value = {}\n const csv = csvData.value!\n const nonSampleCols = csv.columns.filter(c => c !== csv.sampleColumn && c !== 'sample_type')\n if (nonSampleCols.length === 0) {\n // No metadata columns — degenerate, single Unknown class\n tokenized.value = csv.rows.map(() => [])\n classes.value = [{\n kind: 'unknown',\n label: 'Unknown',\n members: csv.rows.map((_, i) => i),\n classTagPositions: [],\n disposition: 'group',\n }]\n schemas.value = { unknown: { classKind: 'unknown', columns: [], groupBy: [] } }\n activeClassKey.value = 'unknown'\n return\n }\n const tokens = csv.rows.map(r => nonSampleCols.map(col => r[col] ?? ''))\n tokenized.value = tokens\n const structuredClass: SampleClass = {\n kind: 'unknown',\n label: 'Unknown',\n members: csv.rows.map((_, i) => i),\n classTagPositions: [],\n disposition: 'group',\n }\n classes.value = [structuredClass]\n\n const schema = buildClassSchema(tokens, 'unknown', undefined, [])\n schema.columns = schema.columns.map((c, i) => ({\n ...c,\n name: nonSampleCols[i] ?? c.name,\n originalName: nonSampleCols[i],\n }))\n // CSV headers are already the user-authored hierarchy. Default to every\n // metadata column so N CSV columns produce N visible grouping levels.\n schema.groupBy = schema.columns.map(c => c.index)\n schemas.value = { unknown: schema }\n activeClassKey.value = 'unknown'\n }\n\n function parseInputFromPaste() {\n const lines = samples.value\n if (lines.length === 0) {\n classes.value = []\n schemas.value = {}\n experiment.value = {}\n return\n }\n // One token row for each sample. Replicate tokens, sample numbers and the\n // injection number stay in the row and get a column role below\n // ('replicate', 'id', 'run-order'), which keeps them out of the default\n // group key. Nothing is removed from a name before the user sees it.\n const batch = tokenizeBatch(lines)\n delimiter.value = batch.delimiter\n experiment.value = batch.experiment\n const tokens = batch.rows\n // Classify by sample type, THEN split each experimental class by token\n // length so every member of a class shares the same field count. This\n // keeps the per-class token matrix rectangular, so column index N means\n // the same field for every sample.\n const detected = splitByTokenLength(\n detectClass(tokens, { sampleTypeHints: sampleTypeHints.value, sharedPrefix: batch.sharedPrefix }),\n tokens,\n )\n const newSchemas: Record<string, ClassSchema> = {}\n for (const cls of detected) {\n let memberTokens = cls.members.map(i => tokens[i])\n let tailPositions: number[] = []\n const experimental = isExperimentalKind(cls.kind)\n if (experimental) {\n // Tokens that hold two fields (`2-1`, `C1`) become two columns.\n const split = splitColumns(memberTokens)\n cls.members.forEach((member, row) => { tokens[member] = split.rows[row] })\n cls.classTagPositions = split.origin.flatMap((source, index) =>\n cls.classTagPositions.includes(source) ? [index] : [],\n )\n memberTokens = split.rows\n tailPositions = split.tail\n }\n const schema = buildClassSchema(\n memberTokens,\n cls.kind,\n cls.subKind,\n cls.classTagPositions,\n tailPositions,\n )\n // A QC-kind class is one group named by the class label, also when the\n // user routes it into the groups.\n if (!experimental) schema.groupBy = []\n newSchemas[classKey(cls)] = { ...schema, tokenLength: cls.tokenLength }\n }\n tokenized.value = tokens\n classes.value = detected\n schemas.value = newSchemas\n if (detected.length > 0) {\n activeClassKey.value = classKey(detected[0])\n }\n }\n\n const activeSchema = computed<ClassSchema | null>(() => schemas.value[activeClassKey.value] ?? null)\n\n const suggestions = computed<MergeSuggestion[]>(() => {\n const schema = activeSchema.value\n if (!schema) return []\n const activeClass = classes.value.find(\n c => classKey(c) === activeClassKey.value,\n )\n if (!activeClass) return []\n const memberTokens = activeClass.members.map(i => tokenized.value[i])\n return findMerges(schema, memberTokens)\n })\n\n function composeWith(withSchemas: Record<string, ClassSchema>): AutoGroupResult {\n return composeGroups({\n tokenizedSamples: tokenized.value,\n sampleNames: samples.value,\n schemas: withSchemas,\n classes: classes.value,\n prefixClassLabels: !(csvData.value && (inputMode.value === 'csv' || inputMode.value === 'experiment')),\n missingGroupName: csvData.value && (inputMode.value === 'csv' || inputMode.value === 'experiment')\n ? STRUCTURED_MISSING_GROUP\n : undefined,\n })\n }\n\n const result = computed<AutoGroupResult>(() => composeWith(schemas.value))\n\n const fingerprint = computed<SchemaFingerprint>(() =>\n serializeFingerprint(result.value.schemas ?? []),\n )\n\n const groups = computed(() => result.value.groups)\n const qcGroups = computed(() => result.value.qcGroups ?? [])\n const metadata = computed(() => result.value.metadata)\n const excludedSamples = computed(() => result.value.excludedSamples)\n\n // ----- Mutators -----\n\n function setClassDisposition(key: string, disposition: ClassDisposition) {\n const target = classes.value.find(c => classKey(c) === key)\n if (!target || target.disposition === disposition) return\n classes.value = classes.value.map(c =>\n classKey(c) === key ? { ...c, disposition } : c,\n )\n }\n\n function setClassDispositions(\n predicate: (c: SampleClass) => boolean,\n disposition: ClassDisposition,\n ) {\n let changed = false\n const next = classes.value.map(c => {\n if (predicate(c) && c.disposition !== disposition) {\n changed = true\n return { ...c, disposition }\n }\n return c\n })\n if (changed) classes.value = next\n }\n\n // `groupBy` is an ORDERED layer list: entry N is hierarchy layer N of the\n // composed group names (layer 1 = the sample selector's major group). A\n // column toggled back on therefore appends as the LAST layer rather than\n // returning to its token position.\n function toggleGroupBy(key: string, idx: number) {\n const schema = schemas.value[key]\n if (!schema) return\n const groupBy = schema.groupBy.includes(idx)\n ? schema.groupBy.filter(i => i !== idx)\n : [...schema.groupBy, idx]\n schemas.value = {\n ...schemas.value,\n [key]: { ...schema, groupBy },\n }\n }\n\n /** Move an enabled column one step up (toward layer 1) or down in the layer order. */\n function moveGroupBy(key: string, idx: number, direction: 'up' | 'down') {\n const schema = schemas.value[key]\n if (!schema) return\n const from = schema.groupBy.indexOf(idx)\n if (from === -1) return\n const to = direction === 'up' ? from - 1 : from + 1\n if (to < 0 || to >= schema.groupBy.length) return\n const groupBy = [...schema.groupBy]\n ;[groupBy[from], groupBy[to]] = [groupBy[to], groupBy[from]]\n schemas.value = {\n ...schemas.value,\n [key]: { ...schema, groupBy },\n }\n }\n\n // The Field Recipe presents ONE set of grouping fields for the whole batch,\n // but classes each own a schema — so a field edit must reach every class that\n // exposes that field, or a multi-class batch ends up with divergent layer\n // orders (half the groups reordered). Fields are matched by display identity\n // rather than column index, since a class split off by token length can carry\n // the same field at a different position.\n function schemasExposing(fieldKey: string): { key: string; index: number }[] {\n return Object.entries(schemas.value).flatMap(([key, schema]) => {\n // An overlay class is one group; its columns are not grouping fields.\n if (!isExperimentalKind(schema.classKind)) return []\n const col = schema.columns.find(c =>\n (c.displayName ?? c.name) === fieldKey\n && (GROUPING_FIELD_ROLES.has(c.role) || schema.groupBy.includes(c.index)),\n )\n return col ? [{ key, index: col.index }] : []\n })\n }\n\n function fieldIdentity(schema: ClassSchema, index: number): string | undefined {\n const column = schema.columns.find(candidate => candidate.index === index)\n return column ? (column.displayName ?? column.name) : undefined\n }\n\n /**\n * Toggle a grouping field across every class exposing it. The batch card is\n * on when any schema groups by the field, so clicking it disables all such\n * schemas; only a fully disabled field is enabled across the batch.\n */\n function toggleGroupByField(fieldKey: string) {\n const targets = schemasExposing(fieldKey)\n if (targets.length === 0) return\n const enable = !targets.some(({ key, index }) => schemas.value[key]?.groupBy.includes(index))\n for (const { key, index } of targets) {\n const schema = schemas.value[key]\n if (!schema || schema.groupBy.includes(index) === enable) continue\n toggleGroupBy(key, index)\n }\n }\n\n /**\n * Rename a grouping field in every class exposing it. A blank name, or a\n * name that a column of any experimental class has, changes nothing: the\n * name is the field's identity across classes, and the cards merge by it.\n */\n function renameGroupByField(fieldKey: string, name: string) {\n const next = name.trim()\n const targets = schemasExposing(fieldKey)\n if (!next || next === fieldKey || targets.length === 0) return\n const taken = Object.values(schemas.value).some(schema =>\n isExperimentalKind(schema.classKind) && schema.columns.some(c => (c.displayName ?? c.name) === next),\n )\n if (taken) return\n for (const { key, index } of targets) setColumnName(key, index, next)\n }\n\n /** Number of experimental groups that remain when the field is switched off in every class. */\n function groupCountWithout(fieldKey: string): number {\n const targets = new Map(schemasExposing(fieldKey).map(({ key, index }) => [key, index]))\n const without = Object.fromEntries(\n Object.entries(schemas.value).map(([key, schema]) => [\n key,\n targets.has(key) ? { ...schema, groupBy: schema.groupBy.filter(i => i !== targets.get(key)) } : schema,\n ]),\n )\n return composeWith(without).experimentalGroups?.length ?? 0\n }\n\n /**\n * Move a grouping field in the active schema, then project that visible field\n * order across every class. Class-only fields keep their relative order after\n * the shared fields because the Field Recipe has no card with which to place\n * them explicitly.\n */\n function moveGroupByField(fieldKey: string, direction: 'up' | 'down') {\n const targets = schemasExposing(fieldKey)\n if (targets.length === 0) return\n const active = targets.find(target =>\n target.key === activeClassKey.value && schemas.value[target.key]?.groupBy.includes(target.index),\n ) ?? targets.find(target => schemas.value[target.key]?.groupBy.includes(target.index)) ?? targets[0]\n const activeSchema = schemas.value[active.key]\n if (!activeSchema) return\n const from = activeSchema.groupBy.indexOf(active.index)\n if (from === -1) return\n const to = direction === 'up' ? from - 1 : from + 1\n if (to < 0 || to >= activeSchema.groupBy.length) return\n\n const activeOrder = [...activeSchema.groupBy]\n ;[activeOrder[from], activeOrder[to]] = [activeOrder[to], activeOrder[from]]\n const desiredRank = new Map(\n activeOrder.flatMap((index, rank) => {\n const identity = fieldIdentity(activeSchema, index)\n return identity === undefined ? [] : [[identity, rank] as const]\n }),\n )\n\n let changed = false\n const nextSchemas = { ...schemas.value }\n for (const [key, schema] of Object.entries(schemas.value)) {\n const shared: Array<{ index: number; rank: number }> = []\n const classOnly: number[] = []\n for (const index of schema.groupBy) {\n const identity = fieldIdentity(schema, index)\n const rank = identity === undefined ? undefined : desiredRank.get(identity)\n if (rank === undefined) classOnly.push(index)\n else shared.push({ index, rank })\n }\n shared.sort((left, right) => left.rank - right.rank)\n const groupBy = [...shared.map(entry => entry.index), ...classOnly]\n if (groupBy.every((index, position) => index === schema.groupBy[position])) continue\n nextSchemas[key] = { ...schema, groupBy }\n changed = true\n }\n if (changed) schemas.value = nextSchemas\n }\n\n function updateColumn(key: string, idx: number, patch: Partial<ColumnInfo>) {\n const schema = schemas.value[key]\n if (!schema) return\n const col = schema.columns.find(c => c.index === idx)\n if (!col) return\n let changed = false\n for (const k of Object.keys(patch) as (keyof ColumnInfo)[]) {\n if ((col as unknown as Record<string, unknown>)[k as string] !== (patch as unknown as Record<string, unknown>)[k as string]) {\n changed = true\n break\n }\n }\n if (!changed) return\n schemas.value = {\n ...schemas.value,\n [key]: {\n ...schema,\n columns: schema.columns.map(c => (c.index === idx ? { ...c, ...patch } : c)),\n },\n }\n }\n\n function setRole(key: string, idx: number, role: ColumnRole) {\n updateColumn(key, idx, { role })\n }\n\n function setColumnName(key: string, idx: number, name: string) {\n updateColumn(key, idx, { displayName: name })\n }\n\n function setValueOps(key: string, idx: number, ops: ValueOps) {\n updateColumn(key, idx, { ops })\n }\n\n function setBinning(key: string, idx: number, binning: NumericBinning) {\n updateColumn(key, idx, { binning })\n }\n\n function mergeColumns(key: string, indices: number[]) {\n const schema = schemas.value[key]\n if (!schema || indices.length < 2) return\n const sorted = [...indices].sort((a, b) => a - b)\n const start = sorted[0]\n const merged: ColumnInfo = {\n index: start,\n name: `Column ${sorted.map(i => i + 1).join('–')}`,\n sourceIndices: sorted.flatMap(i => schema.columns.find(c => c.index === i)?.sourceIndices ?? [i]),\n uniqueValues: [],\n cardinality: 0,\n role: 'factor',\n }\n const dropped = new Set(sorted)\n schemas.value = {\n ...schemas.value,\n [key]: {\n ...schema,\n columns: [merged, ...schema.columns.filter(c => !dropped.has(c.index))]\n .sort((a, b) => a.index - b.index),\n groupBy: schema.groupBy.filter(i => !dropped.has(i) || i === start),\n },\n }\n }\n\n function loadFingerprint(fp: SchemaFingerprint) {\n const restored = restoreFingerprint(fp, Object.values(schemas.value))\n const next: Record<string, ClassSchema> = {}\n for (const s of restored) {\n next[classKey({ kind: s.classKind, subKind: s.subKind, tokenLength: s.tokenLength })] = s\n }\n schemas.value = next\n }\n\n // ----- Template download -----\n\n const canDownloadTemplate = computed(() => samples.value.length > 0)\n\n function downloadTemplate(mode: 'blank' | 'prefilled' = 'prefilled', format: 'csv' | 'tsv' = 'csv') {\n const { content, filename } = composeTemplate(\n samples.value,\n mode === 'prefilled' ? Object.values(schemas.value) : null,\n mode === 'prefilled' ? classes.value : null,\n { mode, format, tokenizedSamples: tokenized.value },\n )\n const blob = new Blob([content], { type: format === 'tsv' ? 'text/tab-separated-values' : 'text/csv' })\n const url = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = url\n a.download = filename\n document.body.appendChild(a)\n a.click()\n document.body.removeChild(a)\n URL.revokeObjectURL(url)\n }\n\n /** Test seam — exposes the template content/filename without triggering a browser download. */\n function composeTemplateForTest(opts: Pick<TemplateOptions, 'mode' | 'format'>) {\n return composeTemplate(\n samples.value,\n opts.mode === 'prefilled' ? Object.values(schemas.value) : null,\n opts.mode === 'prefilled' ? classes.value : null,\n { ...opts, tokenizedSamples: tokenized.value },\n )\n }\n\n // ----- Legacy public refs and shims -----\n\n const outliers = ref<OutlierInfo[]>([])\n const hasOutliers = computed(() => outliers.value.length > 0)\n const conformingSamples = computed(() => samples.value)\n const dominantFieldCount = computed(() => tokenized.value[0]?.length ?? 0)\n const minFieldCount = computed(() => {\n if (tokenized.value.length === 0) return 0\n return Math.min(...tokenized.value.map(t => t.length))\n })\n const fields = computed(() => activeSchema.value?.columns ?? [])\n const fieldNames = computed<Record<number, string>>(() => {\n const out: Record<number, string> = {}\n for (const c of fields.value) out[c.index] = c.displayName ?? c.name\n return out\n })\n const enabledFields = computed<Set<number>>(() =>\n activeSchema.value ? new Set(activeSchema.value.groupBy) : new Set(),\n )\n const effectiveColumns = fields\n const allSingletons = computed(\n () => groups.value.length > 1 && groups.value.every(g => g.samples.length === 1),\n )\n\n function loadExperimentData(\n rawData: Record<string, unknown>,\n options: LoadExperimentDataOptions = {},\n ): boolean {\n const parsed = extractSamplesFromDesignData(rawData, options)\n if (!parsed) return false\n inputMode.value = 'experiment'\n csvData.value = parsed\n sampleTypeHints.value = parsed.sampleTypeHints\n parseInput()\n return true\n }\n\n function reset() {\n rawText.value = ''\n csvData.value = null\n classes.value = []\n schemas.value = {}\n activeClassKey.value = ''\n tokenized.value = []\n experiment.value = {}\n outliers.value = []\n sampleTypeHints.value = []\n }\n\n return {\n // State\n inputMode, rawText, csvData, sampleTypeHints,\n classes, schemas, activeClassKey,\n delimiter, experiment, dominantFieldCount, minFieldCount,\n // Computed\n samples, tokenized, activeSchema, suggestions,\n groups, qcGroups, metadata, excludedSamples, result, fingerprint,\n fields, fieldNames, enabledFields, effectiveColumns,\n hasOutliers, outliers, conformingSamples, allSingletons,\n canDownloadTemplate,\n // Mutators\n setClassDisposition, setClassDispositions, toggleGroupBy, moveGroupBy,\n toggleGroupByField, moveGroupByField, renameGroupByField, groupCountWithout,\n setRole, setColumnName, setValueOps, setBinning,\n mergeColumns, loadFingerprint,\n // Actions\n parseInput, downloadTemplate, composeTemplateForTest, loadExperimentData, reset,\n }\n}\n\n/**\n * Reads experiment `design_data` samples into the `useAutoGroup` CSV shape.\n */\nexport function extractSamplesFromDesignData(\n rawData: Record<string, unknown>,\n options: LoadExperimentDataOptions = {},\n): ParsedExperimentCsvData | null {\n const designData = unwrapExperimentDesignData(rawData)\n if (!designData) return null\n const samples = designData.samples\n if (!Array.isArray(samples) || samples.length === 0) return null\n const allConditionKeys: string[] = []\n const keySet = new Set<string>()\n const sampleTypeHints: (string | undefined)[] = []\n for (const sample of samples) {\n const rawType = (sample as Record<string, unknown>).sample_type\n sampleTypeHints.push(typeof rawType === 'string' ? rawType.toLowerCase() : undefined)\n const conditions = (sample as Record<string, unknown>).conditions as Record<string, string> | undefined\n if (conditions && typeof conditions === 'object') {\n for (const key of Object.keys(conditions)) {\n if (!keySet.has(key)) {\n keySet.add(key)\n allConditionKeys.push(key)\n }\n }\n }\n }\n if (allConditionKeys.length === 0) return null\n const columns = ['sample_name', ...allConditionKeys, 'sample_type']\n const rows = (samples as Record<string, unknown>[]).map(s => {\n const conditions = (s.conditions as Record<string, string>) ?? {}\n const row: Record<string, string> = {\n sample_name: String(s.sample_name ?? ''),\n sample_type: String(s.sample_type ?? 'sample'),\n }\n for (const key of allConditionKeys) row[key] = conditions[key] ?? ''\n return row\n })\n return constrainParsedSamplesToInput(\n { columns, rows, sampleColumn: 'sample_name', delimiter: ',', sampleTypeHints },\n options.samples,\n )\n}\n\nfunction constrainParsedSamplesToInput(\n parsed: ParsedExperimentCsvData,\n samples: readonly string[] | undefined,\n): ParsedExperimentCsvData {\n const requested = samples\n ?.map(sample => sample.trim())\n .filter(sample => sample.length > 0)\n if (!requested || requested.length === 0) return parsed\n\n const rowBySample = new Map<string, { row: Record<string, string>; hint: string | undefined }>()\n parsed.rows.forEach((row, index) => {\n const sample = row[parsed.sampleColumn]\n if (sample && !rowBySample.has(sample)) {\n rowBySample.set(sample, { row, hint: parsed.sampleTypeHints[index] })\n }\n })\n\n const metadataColumns = parsed.columns.filter(\n column => column !== parsed.sampleColumn && column !== 'sample_type',\n )\n const rows: Record<string, string>[] = []\n const sampleTypeHints: (string | undefined)[] = []\n\n for (const sample of requested) {\n const match = rowBySample.get(sample)\n if (match) {\n rows.push(match.row)\n sampleTypeHints.push(match.hint)\n continue\n }\n\n const outlierRow: Record<string, string> = {\n [parsed.sampleColumn]: sample,\n sample_type: 'sample',\n }\n for (const column of metadataColumns) outlierRow[column] = ''\n rows.push(outlierRow)\n sampleTypeHints.push(undefined)\n }\n\n return { ...parsed, rows, sampleTypeHints }\n}\n","/**\n * Pure adapters mapping the `useAutoGroup` engine state to the controlled\n * Smart Group UI (`SmartGroupFieldRecipe` / `SmartGroupManual`) and back to an\n * `AutoGroupResult`. Framework-free so the mapping is unit-testable in isolation\n * from `AutoGroupModalInternal.vue`.\n */\nimport type { AutoGroupResult, ClassSchema, ColumnInfo, ColumnRole, SampleClass } from '../../types/auto-group'\nimport type { SampleGroup } from '../../types'\nimport type { FieldRecipeGroup, QcRoute, SmartGroupField } from '../SmartGroupFieldRecipe.groups'\nimport type { ManualSample } from '../SmartGroupManual.cohorts'\nimport type { FieldRecipeAnatomyToken, FieldRecipeQcChip, SmartGroupSeed } from '../SmartGroup.types'\nimport { GROUPING_FIELD_ROLES, classKey, isExperimentalKind } from './autoGroup'\nimport type { ExperimentHeader } from './autoGroup'\n\nconst PREVIEW_VALUES = 3\n\n/** Value preview of a field card: the first three values, then the count of the rest. */\nexport function previewValues(values: string[]): string {\n const shown = values.slice(0, PREVIEW_VALUES).join(', ')\n return values.length > PREVIEW_VALUES ? `${shown} … +${values.length - PREVIEW_VALUES}` : shown\n}\n\n/** Experiment line of the modal header: `exp157 · 2025-10-13 · LT`. */\nexport function experimentLabel(header: ExperimentHeader): string {\n return [header.code, header.date, header.operator].filter(Boolean).join(' · ')\n}\n\n/**\n * Names of all columns of the experimental classes. A field cannot take one\n * of them: the engine merges fields by name.\n */\nexport function engineColumnNames(schemas: Record<string, ClassSchema>): string[] {\n const names = Object.values(schemas)\n .filter(schema => isExperimentalKind(schema.classKind))\n .flatMap(schema => schema.columns.map(column => column.displayName ?? column.name))\n return [...new Set(names)]\n}\n\n/** One chip for each QC-type class, with its sample count. */\nexport function engineQcChips(classes: SampleClass[]): FieldRecipeQcChip[] {\n return classes\n .filter(c => !isExperimentalKind(c.kind))\n .map(c => ({ name: c.label, count: c.members.length }))\n}\n\n/**\n * The tokens of one study sample with the role the engine gave each of them,\n * so the user sees how the names were read. The sample is the first member of\n * the largest experimental class. `cards` supplies the on/off state and the\n * layer of the tokens that are grouping fields.\n */\nexport function engineAnatomy(\n classes: SampleClass[],\n schemas: Record<string, ClassSchema>,\n tokenized: string[][],\n cards: { fields: SmartGroupField[]; layers: (number | null)[] },\n): FieldRecipeAnatomyToken[] {\n const cls = classes\n .filter(c => isExperimentalKind(c.kind) && schemas[classKey(c)])\n .sort((a, b) => b.members.length - a.members.length)[0]\n if (!cls) return []\n const tokens = tokenized[cls.members[0]] ?? []\n return schemas[classKey(cls)].columns.flatMap((column): FieldRecipeAnatomyToken[] => {\n const text = column.sourceIndices.map(index => tokens[index] ?? '').join('_')\n if (!text) return []\n const name = column.displayName ?? column.name\n const card = cards.fields.findIndex(field => field.key === name)\n if (card === -1) {\n // A token that is not a field shows an inferred name only when the\n // name says something (Date, Injection #); `Username` or `Matrix 2` on a\n // constant token does not.\n const caption = /^(Field \\d+|Username.*|.* \\d+)$/.test(name) ? roleLabel(column.role).toLowerCase() : name\n return [{ text, caption, state: 'fixed' }]\n }\n const layer = cards.layers[card]\n return [layer === null ? { text, caption: name, state: 'off' } : { text, caption: String(layer), state: 'on' }]\n })\n}\n\nconst ROLE_LABELS: Record<ColumnRole, string> = {\n 'constant': 'Constant',\n 'factor': 'Factor',\n 'replicate': 'Replicate',\n 'run-order': 'Run',\n 'id': 'Sample ID',\n 'numeric': 'Numeric',\n 'class-tag': 'Class tag',\n 'ignore': 'Ignore',\n}\n\n/** Human label for a column role; defaults to `Factor` when unset. */\nexport function roleLabel(role?: ColumnRole): string {\n return role ? ROLE_LABELS[role] : 'Factor'\n}\n\n/**\n * Columns worth offering as grouping toggles: factors, numerics, replicates and\n * sample IDs (the last two are off by default), plus any column already enabled\n * in `groupBy` (so a manual group-by choice still shows).\n * Preserves the engine's column order. `groupBy` is the schema's ORDERED layer\n * list — entry N is hierarchy layer N of the composed group names.\n */\nexport function candidateColumns(columns: ColumnInfo[], groupBy: readonly number[]): ColumnInfo[] {\n const enabled = new Set(groupBy)\n return columns.filter(c => GROUPING_FIELD_ROLES.has(c.role) || enabled.has(c.index))\n}\n\n/**\n * Map engine columns to Field Recipe cards + their on/off state (same order).\n * `layers` aligns with the cards: the column's 1-based position in the\n * `groupBy` layer order, or `null` when the column is not grouped by.\n */\nexport function engineFieldsToCards(\n columns: ColumnInfo[],\n groupBy: readonly number[],\n): { fields: SmartGroupField[]; enabled: boolean[]; layers: (number | null)[] } {\n const candidates = candidateColumns(columns, groupBy)\n return {\n fields: candidates.map(c => ({\n key: c.displayName ?? c.name,\n role: roleLabel(c.role),\n vals: previewValues(c.uniqueValues),\n rep: c.role === 'replicate' || c.role === 'id',\n })),\n enabled: candidates.map(c => groupBy.includes(c.index)),\n layers: candidates.map(c => {\n const pos = groupBy.indexOf(c.index)\n return pos === -1 ? null : pos + 1\n }),\n }\n}\n\n/**\n * Build the batch-wide field recipe from every class schema. The active schema\n * stays first for a stable card order; fields and enabled hierarchy layers that\n * only exist in another token-length/class split are appended instead of being\n * hidden from the controlled UI.\n */\nexport function engineSchemasToCards(\n schemas: Record<string, ClassSchema>,\n activeClassKey: string,\n): { fields: SmartGroupField[]; enabled: boolean[]; layers: (number | null)[] } {\n const entries = Object.entries(schemas)\n const active = entries.find(([key]) => key === activeClassKey)\n const ordered = active\n ? [active, ...entries.filter(([key]) => key !== activeClassKey)]\n : entries\n const candidates = new Map<string, ColumnInfo>()\n const enabledOrder: string[] = []\n const enabled = new Set<string>()\n\n for (const [, schema] of ordered) {\n // An overlay class (QC, blank, standard) is one group; its columns are not fields.\n if (!isExperimentalKind(schema.classKind)) continue\n for (const index of schema.groupBy) {\n const column = schema.columns.find(candidate => candidate.index === index)\n if (!column) continue\n const identity = column.displayName ?? column.name\n if (!enabled.has(identity)) {\n enabled.add(identity)\n enabledOrder.push(identity)\n }\n }\n for (const column of candidateColumns(schema.columns, schema.groupBy)) {\n const identity = column.displayName ?? column.name\n if (!candidates.has(identity)) candidates.set(identity, column)\n }\n }\n\n const cards = [...candidates.entries()]\n return {\n fields: cards.map(([key, column]) => ({\n key,\n role: roleLabel(column.role),\n vals: previewValues(column.uniqueValues),\n rep: column.role === 'replicate' || column.role === 'id',\n })),\n enabled: cards.map(([key]) => enabled.has(key)),\n layers: cards.map(([key]) => {\n const position = enabledOrder.indexOf(key)\n return position === -1 ? null : position + 1\n }),\n }\n}\n\n/** Map engine sample groups to the live preview rail shape. */\nexport function engineGroupsToRail(groups: SampleGroup[]): FieldRecipeGroup[] {\n return groups.map(g => ({ name: g.name, count: g.samples.length, color: g.color }))\n}\n\nconst ROUTE_TO_ENGINE: Record<QcRoute, 'overlay' | 'exclude' | 'mix'> = {\n 'Overlay only': 'overlay',\n 'Exclude': 'exclude',\n 'Mix into groups': 'mix',\n}\nconst ENGINE_TO_ROUTE: Record<'overlay' | 'exclude' | 'mix', QcRoute> = {\n overlay: 'Overlay only',\n exclude: 'Exclude',\n mix: 'Mix into groups',\n}\n\n/** Map the Field Recipe QC label to the engine's class disposition routing. */\nexport function qcRouteToEngine(route: QcRoute): 'overlay' | 'exclude' | 'mix' {\n return ROUTE_TO_ENGINE[route]\n}\n\n/** Map the engine's QC routing back to the Field Recipe label. */\nexport function engineToQcRoute(routing: 'overlay' | 'exclude' | 'mix'): QcRoute {\n return ENGINE_TO_ROUTE[routing]\n}\n\n/**\n * Seed the manual cohort builder from existing groups. The hierarchical group\n * name (`group/sub/sub2`) splits into the three cohort levels; colour carries\n * over. One seed entry per sample. Names with more than three `/`-segments keep\n * the remainder in `sub2` (rejoined with `/`) so the round-trip is lossless —\n * `manualSamplesToResult` re-joins on `/`.\n */\nexport function seedFromGroups(groups: SampleGroup[]): Record<string, SmartGroupSeed> {\n const seed: Record<string, SmartGroupSeed> = {}\n for (const group of groups) {\n const parts = group.name.split('/')\n const grp = parts[0]\n const sub = parts[1]\n const sub2 = parts.length > 3 ? parts.slice(2).join('/') : parts[2]\n for (const sample of group.samples) {\n seed[sample] = {\n group: grp,\n sub: sub || undefined,\n sub2: sub2 || undefined,\n color: group.color,\n }\n }\n }\n return seed\n}\n\n/**\n * Convert the manual cohort assignments back into an `AutoGroupResult`.\n * `baseGroups` (the modal's incoming `groups` prop) are preserved in their\n * original order and colour — including any that end up empty — so re-opening\n * the modal and moving samples between cohorts is non-destructive. Newly created\n * cohorts are appended in first-seen order.\n */\nexport function manualSamplesToResult(\n samples: ManualSample[],\n baseGroups: SampleGroup[] = [],\n): AutoGroupResult {\n const assigned = new Map<string, { color: string; samples: string[] }>()\n const order: string[] = []\n for (const sample of samples) {\n if (!sample.group) continue\n const name = [sample.group, sample.sub, sample.sub2].filter(Boolean).join('/')\n if (!assigned.has(name)) {\n assigned.set(name, { color: sample.color ?? '', samples: [] })\n order.push(name)\n }\n assigned.get(name)!.samples.push(sample.name)\n }\n\n const groups: SampleGroup[] = []\n const consumed = new Set<string>()\n for (const base of baseGroups) {\n consumed.add(base.name)\n groups.push({ name: base.name, color: base.color, samples: assigned.get(base.name)?.samples ?? [] })\n }\n for (const name of order) {\n if (consumed.has(name)) continue\n const entry = assigned.get(name)!\n groups.push({ name, color: entry.color, samples: entry.samples })\n }\n\n return { groups, experimentalGroups: groups, qcGroups: [], metadata: [], excludedSamples: [] }\n}\n","import { DEFAULT_COLORS } from './internal/autoGroup/colors'\nimport type { SampleGroup } from '../types'\n\nexport type ColorEdit =\n | { kind: 'single'; name: string }\n | { kind: 'family'; names: string[] }\n\nexport const DEFAULT_COLOR_PICKER_SEED = '#3B82F6'\nexport const SAMPLE_GROUP_COLOR_OPTIONS = DEFAULT_COLORS.slice(0, 5)\n\nexport function applySampleGroupColorEdit(\n groups: SampleGroup[],\n edit: ColorEdit,\n color: string,\n): SampleGroup[] {\n const targets = edit.kind === 'family' ? new Set(edit.names) : new Set([edit.name])\n return groups.map(group =>\n targets.has(group.name) ? { ...group, color } : group,\n )\n}\n\nexport function getSampleGroupColorEditSeed(\n edit: ColorEdit | null,\n getGroupColor: (groupName: string) => string,\n): string {\n if (!edit) return DEFAULT_COLOR_PICKER_SEED\n return getGroupColor(edit.kind === 'family' ? edit.names[0] : edit.name)\n}\n\nexport function pickUnusedSampleGroupColor(groups: SampleGroup[]): string {\n const usedColors = new Set(groups.map(group => group.color))\n return DEFAULT_COLORS.find(color => !usedColors.has(color)) || DEFAULT_COLORS[0]\n}\n\nexport function createSampleGroup(\n name: string,\n existingGroups: SampleGroup[],\n color?: string,\n): SampleGroup | null {\n const trimmedName = name.trim()\n if (!trimmedName) return null\n\n return {\n name: trimmedName,\n color: color || pickUnusedSampleGroupColor(existingGroups),\n samples: [],\n }\n}\n","<script setup lang=\"ts\">\n/**\n * Smart Group modal. Parses sample naming patterns into candidate grouping\n * fields (auto / \"Field Recipe\") or lets you assign cohorts by hand (manual),\n * staying wired to the `useAutoGroup` engine. The UI is the Smart Group design\n * (`SmartGroupFieldRecipe` / `SmartGroupManual`) driven in controlled mode; this\n * component is the adapter that maps engine state ↔ that UI and emits the\n * resulting `AutoGroupResult`. Private to `SampleSelector`, which emits that\n * result as its `smartGroup` event.\n */\nimport { ref, computed, watch } from 'vue'\nimport BaseModal from '../BaseModal.vue'\nimport AlertBox from '../AlertBox.vue'\nimport SmartGroupFieldRecipe from '../SmartGroupFieldRecipe.vue'\nimport SmartGroupManual from '../SmartGroupManual.vue'\nimport { useAutoGroup, parseCSV } from './useAutoGroup'\nimport { useApi } from '../../composables/useApi'\nimport { readFileAsText } from '../../composables/useFileImport'\nimport {\n engineAnatomy,\n engineColumnNames,\n engineGroupsToRail,\n engineQcChips,\n engineSchemasToCards,\n engineToQcRoute,\n experimentLabel,\n manualSamplesToResult,\n qcRouteToEngine,\n seedFromGroups,\n} from './AutoGroupModalInternal.adapter'\nimport { SAMPLE_GROUP_COLOR_OPTIONS } from '../SampleSelector.colors'\nimport { mostlySingletons, type QcRoute } from '../SmartGroupFieldRecipe.groups'\nimport type { ManualSample } from '../SmartGroupManual.cohorts'\nimport type { ManualPaletteEntry } from '../SmartGroup.types'\nimport type { AutoGroupResult } from '../../types/auto-group'\nimport type { SampleGroup } from '../../types'\n\ntype GroupingWorkflow = 'auto' | 'manual'\n\nexport interface Props {\n open: boolean\n samples?: string[]\n groups?: SampleGroup[]\n initialMode?: GroupingWorkflow\n experimentId?: number\n /** Pre-fetched design data — bypasses API fetch when provided */\n designData?: Record<string, unknown>\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n samples: () => [],\n groups: () => [],\n initialMode: 'auto',\n experimentId: undefined,\n designData: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:open': [value: boolean]\n 'apply': [result: AutoGroupResult]\n}>()\n\nconst autoGroup = useAutoGroup()\nconst api = useApi()\nconst activeWorkflow = ref<GroupingWorkflow>(props.initialMode)\nconst experimentError = ref<string | null>(null)\nconst experimentLoading = ref(false)\n\n// QC routing — a 3-way state stored as two booleans so downstream engine\n// dispositions stay stable; the UI presents it as overlay / exclude / mix.\nconst qcAsOverlay = ref(true)\nconst excludeQc = ref(false)\ntype QcRouting = 'overlay' | 'exclude' | 'mix'\nconst qcRouting = computed<QcRouting>(() => {\n if (excludeQc.value) return 'exclude'\n if (qcAsOverlay.value) return 'overlay'\n return 'mix'\n})\nfunction setQcRouting(value: QcRouting) {\n qcAsOverlay.value = value === 'overlay'\n excludeQc.value = value === 'exclude'\n autoGroup.setClassDispositions(\n c => c.kind !== 'biological' && c.kind !== 'unknown',\n value === 'exclude' ? 'exclude' : value === 'overlay' ? 'overlay' : 'group',\n )\n}\n\n// ── Auto mode (Field Recipe) ─────────────────────────────────────────\n// The recipe represents the whole batch, including fields that only exist in\n// another class or token-length schema split.\nconst fieldCards = computed(() =>\n engineSchemasToCards(autoGroup.schemas.value, autoGroup.activeClassKey.value),\n)\nconst railGroups = computed(() =>\n engineGroupsToRail(autoGroup.result.value.experimentalGroups ?? autoGroup.groups.value),\n)\nconst qcSampleCount = computed(() =>\n autoGroup.classes.value\n .filter(c => c.kind !== 'biological' && c.kind !== 'unknown')\n .reduce((acc, c) => acc + c.members.length, 0),\n)\nconst qcChips = computed(() => (qcRouting.value === 'mix' ? [] : engineQcChips(autoGroup.classes.value)))\nconst fieldRecipeRoute = computed<QcRoute>(() => engineToQcRoute(qcRouting.value))\nconst headerCount = computed(() => autoGroup.samples.value.length)\nconst headerLabel = computed(() => experimentLabel(autoGroup.experiment.value))\nconst reservedNames = computed(() => engineColumnNames(autoGroup.schemas.value))\n// The example name is shown for pasted names only: CSV and experiment input\n// have columns, not name tokens.\nconst anatomy = computed(() =>\n autoGroup.inputMode.value === 'paste'\n ? engineAnatomy(autoGroup.classes.value, autoGroup.schemas.value, autoGroup.tokenized.value, fieldCards.value)\n : [],\n)\n// A design with no replicate in the names gives groups of one sample. Each\n// field that is on then shows how many groups remain when the user switches\n// it off, so the user can find the field that is not part of the condition.\n// The rule is the one of the summary callout, so the two show together.\nconst fieldHints = computed(() => {\n const needsHelp = mostlySingletons(railGroups.value)\n // ponytail: one full composeGroups for each field that is on (a few ms for\n // 200 samples). Count distinct group keys instead if a large batch is slow.\n return fieldCards.value.fields.map((field, index) => {\n if (!needsHelp || !fieldCards.value.enabled[index]) return null\n const remaining = autoGroup.groupCountWithout(field.key)\n return `off → ${remaining} ${remaining === 1 ? 'group' : 'groups'}`\n })\n})\n\n// The cards describe the batch, not one class, so edits fan out to every class\n// exposing the field — otherwise a multi-class batch (e.g. a Liver class and a\n// Kidney class) ends up with only half its groups reordered.\nfunction onToggleField(cardIndex: number) {\n const field = fieldCards.value.fields[cardIndex]\n if (!field) return\n autoGroup.toggleGroupByField(field.key)\n}\nfunction onMoveField(cardIndex: number, direction: 'up' | 'down') {\n const field = fieldCards.value.fields[cardIndex]\n if (!field) return\n autoGroup.moveGroupByField(field.key, direction)\n}\nfunction onRenameField(cardIndex: number, name: string) {\n const field = fieldCards.value.fields[cardIndex]\n if (!field) return\n autoGroup.renameGroupByField(field.key, name)\n}\nfunction onRouteChange(route: QcRoute) {\n setQcRouting(qcRouteToEngine(route))\n}\nfunction applyAuto() {\n emit('apply', autoGroup.result.value)\n close()\n}\n\n// CSV upload from the Field Recipe header: parse the file into the engine's CSV\n// path. `samples` derives reactively from csvData and `parseInput()` builds the\n// classes/fields/groups; re-apply the current QC routing like the open watcher.\nasync function onCsvFile(file: File) {\n experimentError.value = null\n try {\n // Parse BEFORE mutating engine state so a malformed/empty/header-only CSV\n // (parseCSV throws) leaves the engine untouched instead of half-set.\n const text = await readFileAsText(file)\n const parsed = parseCSV(text)\n autoGroup.csvData.value = parsed\n autoGroup.inputMode.value = 'csv'\n autoGroup.parseInput()\n setQcRouting(qcRouting.value)\n } catch (e) {\n experimentError.value = e instanceof Error ? e.message : 'Could not read that CSV file.'\n }\n}\n\n// Template downloads via the engine. `downloadTemplate(mode, format)` defaults to\n// ('prefilled', 'csv'); pass the explicit mode for each menu action.\nfunction onDownloadTemplate() {\n autoGroup.downloadTemplate('blank', 'csv')\n}\nfunction onDownloadPrefilled() {\n autoGroup.downloadTemplate('prefilled', 'csv')\n}\n\n// ── Manual mode ──────────────────────────────────────────────────────\n// Hex swatches (the SDK's existing manual palette) rather than SmartGroupManual's\n// var(--mint-sample-1) … token story default: SampleSelector tints group colours via hex8 string\n// concatenation (`group.color + '20'`), so an emitted cohort colour must be a\n// concrete hex value — matching the colours the previous manual modal produced.\nconst MANUAL_SWATCH_LABELS = ['Blue', 'Green', 'Amber', 'Red', 'Violet']\nconst manualPalette: ManualPaletteEntry[] = SAMPLE_GROUP_COLOR_OPTIONS.map((c, i) => ({\n c,\n k: MANUAL_SWATCH_LABELS[i] ?? c,\n}))\n// Source from the engine once input is parsed (paste of props.samples, an\n// experiment fetch, OR an uploaded CSV) so CSV-loaded samples reach manual mode\n// too; fall back to the raw prop before the engine has parsed anything.\nconst manualSampleNames = computed(() =>\n autoGroup.samples.value.length > 0 ? autoGroup.samples.value : props.samples,\n)\nconst manualSeed = computed(() => seedFromGroups(props.groups))\n// Remount the manual builder when the sample set changes so it re-seeds from\n// freshly-loaded experiment data rather than a stale snapshot.\nconst manualKey = computed(() => manualSampleNames.value.join('\\n'))\nfunction applyManual(payload: { samples: ManualSample[] }) {\n emit('apply', manualSamplesToResult(payload.samples, props.groups))\n close()\n}\n\nfunction close() {\n emit('update:open', false)\n}\n\n// A close or a newer fetch makes the response of an earlier fetch obsolete.\nlet fetchGeneration = 0\n\nasync function fetchExperimentData() {\n if (!props.experimentId && !props.designData) return\n const generation = ++fetchGeneration\n autoGroup.inputMode.value = 'experiment'\n experimentError.value = null\n experimentLoading.value = true\n try {\n let pluginData: Record<string, unknown> | undefined\n if (props.designData) {\n pluginData = props.designData\n } else {\n const result = await api.get<Record<string, unknown>>(`/experiments/${props.experimentId}/data`)\n pluginData = result.data as Record<string, unknown> | undefined\n if (generation !== fetchGeneration) return\n }\n if (!pluginData || typeof pluginData !== 'object') {\n experimentError.value = 'No design data found for this experiment'\n return\n }\n if (!autoGroup.loadExperimentData(pluginData, { samples: props.samples })) {\n experimentError.value = 'No sample metadata found in this experiment'\n return\n }\n setQcRouting(qcRouting.value)\n } catch (e) {\n if (generation === fetchGeneration) {\n experimentError.value = e instanceof Error ? e.message : 'Failed to fetch experiment data'\n }\n } finally {\n if (generation === fetchGeneration) experimentLoading.value = false\n }\n}\n\n// Populate the engine from props when the modal opens. Sample ingestion still\n// comes from the `samples` / `designData` / `experimentId` props exactly as\n// before — the redesign just drops the multi-step input/schema/merge UI.\nwatch(\n () => props.open,\n (open) => {\n if (!open) {\n fetchGeneration += 1\n experimentLoading.value = false\n return\n }\n activeWorkflow.value = props.initialMode\n experimentError.value = null\n qcAsOverlay.value = true\n excludeQc.value = false\n autoGroup.reset()\n if (props.experimentId || props.designData) {\n fetchExperimentData()\n } else if (props.samples.length > 0) {\n autoGroup.rawText.value = props.samples.join('\\n')\n autoGroup.inputMode.value = 'paste'\n autoGroup.parseInput()\n setQcRouting(qcRouting.value)\n } else {\n autoGroup.inputMode.value = 'paste'\n }\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <BaseModal\n class=\"sg-host\"\n :open=\"open\"\n :closable=\"false\"\n :close-on-overlay=\"false\"\n :close-on-escape=\"false\"\n size=\"lg\"\n @update:open=\"emit('update:open', $event)\"\n >\n <AlertBox v-if=\"experimentError\" tone=\"error\" class=\"sg-host__error\">\n {{ experimentError }}\n </AlertBox>\n\n <SmartGroupFieldRecipe\n v-if=\"activeWorkflow === 'auto'\"\n :fields=\"fieldCards.fields\"\n :enabled=\"fieldCards.enabled\"\n :layers=\"fieldCards.layers\"\n :groups=\"railGroups\"\n :qc-chips=\"qcChips\"\n :route=\"fieldRecipeRoute\"\n :header-count=\"headerCount\"\n :qc-count=\"qcSampleCount\"\n :can-download-template=\"autoGroup.canDownloadTemplate.value\"\n :loading=\"experimentLoading\"\n :experiment-label=\"headerLabel\"\n :anatomy=\"anatomy\"\n :hints=\"fieldHints\"\n :reserved-names=\"reservedNames\"\n renamable\n @toggle=\"onToggleField\"\n @move=\"onMoveField\"\n @rename=\"onRenameField\"\n @update:route=\"onRouteChange\"\n @csv-file=\"onCsvFile\"\n @download-template=\"onDownloadTemplate\"\n @download-prefilled=\"onDownloadPrefilled\"\n @apply=\"applyAuto\"\n @manual=\"activeWorkflow = 'manual'\"\n @cancel=\"close\"\n @close=\"close\"\n />\n <SmartGroupManual\n v-else\n :key=\"manualKey\"\n :sample-names=\"manualSampleNames\"\n :seed=\"manualSeed\"\n :palette=\"manualPalette\"\n @done=\"applyManual\"\n @auto=\"activeWorkflow = 'auto'\"\n @cancel=\"close\"\n @close=\"close\"\n />\n </BaseModal>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Smart Group modal. Parses sample naming patterns into candidate grouping\n * fields (auto / \"Field Recipe\") or lets you assign cohorts by hand (manual),\n * staying wired to the `useAutoGroup` engine. The UI is the Smart Group design\n * (`SmartGroupFieldRecipe` / `SmartGroupManual`) driven in controlled mode; this\n * component is the adapter that maps engine state ↔ that UI and emits the\n * resulting `AutoGroupResult`. Private to `SampleSelector`, which emits that\n * result as its `smartGroup` event.\n */\nimport { ref, computed, watch } from 'vue'\nimport BaseModal from '../BaseModal.vue'\nimport AlertBox from '../AlertBox.vue'\nimport SmartGroupFieldRecipe from '../SmartGroupFieldRecipe.vue'\nimport SmartGroupManual from '../SmartGroupManual.vue'\nimport { useAutoGroup, parseCSV } from './useAutoGroup'\nimport { useApi } from '../../composables/useApi'\nimport { readFileAsText } from '../../composables/useFileImport'\nimport {\n engineAnatomy,\n engineColumnNames,\n engineGroupsToRail,\n engineQcChips,\n engineSchemasToCards,\n engineToQcRoute,\n experimentLabel,\n manualSamplesToResult,\n qcRouteToEngine,\n seedFromGroups,\n} from './AutoGroupModalInternal.adapter'\nimport { SAMPLE_GROUP_COLOR_OPTIONS } from '../SampleSelector.colors'\nimport { mostlySingletons, type QcRoute } from '../SmartGroupFieldRecipe.groups'\nimport type { ManualSample } from '../SmartGroupManual.cohorts'\nimport type { ManualPaletteEntry } from '../SmartGroup.types'\nimport type { AutoGroupResult } from '../../types/auto-group'\nimport type { SampleGroup } from '../../types'\n\ntype GroupingWorkflow = 'auto' | 'manual'\n\nexport interface Props {\n open: boolean\n samples?: string[]\n groups?: SampleGroup[]\n initialMode?: GroupingWorkflow\n experimentId?: number\n /** Pre-fetched design data — bypasses API fetch when provided */\n designData?: Record<string, unknown>\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n samples: () => [],\n groups: () => [],\n initialMode: 'auto',\n experimentId: undefined,\n designData: undefined,\n})\n\nconst emit = defineEmits<{\n 'update:open': [value: boolean]\n 'apply': [result: AutoGroupResult]\n}>()\n\nconst autoGroup = useAutoGroup()\nconst api = useApi()\nconst activeWorkflow = ref<GroupingWorkflow>(props.initialMode)\nconst experimentError = ref<string | null>(null)\nconst experimentLoading = ref(false)\n\n// QC routing — a 3-way state stored as two booleans so downstream engine\n// dispositions stay stable; the UI presents it as overlay / exclude / mix.\nconst qcAsOverlay = ref(true)\nconst excludeQc = ref(false)\ntype QcRouting = 'overlay' | 'exclude' | 'mix'\nconst qcRouting = computed<QcRouting>(() => {\n if (excludeQc.value) return 'exclude'\n if (qcAsOverlay.value) return 'overlay'\n return 'mix'\n})\nfunction setQcRouting(value: QcRouting) {\n qcAsOverlay.value = value === 'overlay'\n excludeQc.value = value === 'exclude'\n autoGroup.setClassDispositions(\n c => c.kind !== 'biological' && c.kind !== 'unknown',\n value === 'exclude' ? 'exclude' : value === 'overlay' ? 'overlay' : 'group',\n )\n}\n\n// ── Auto mode (Field Recipe) ─────────────────────────────────────────\n// The recipe represents the whole batch, including fields that only exist in\n// another class or token-length schema split.\nconst fieldCards = computed(() =>\n engineSchemasToCards(autoGroup.schemas.value, autoGroup.activeClassKey.value),\n)\nconst railGroups = computed(() =>\n engineGroupsToRail(autoGroup.result.value.experimentalGroups ?? autoGroup.groups.value),\n)\nconst qcSampleCount = computed(() =>\n autoGroup.classes.value\n .filter(c => c.kind !== 'biological' && c.kind !== 'unknown')\n .reduce((acc, c) => acc + c.members.length, 0),\n)\nconst qcChips = computed(() => (qcRouting.value === 'mix' ? [] : engineQcChips(autoGroup.classes.value)))\nconst fieldRecipeRoute = computed<QcRoute>(() => engineToQcRoute(qcRouting.value))\nconst headerCount = computed(() => autoGroup.samples.value.length)\nconst headerLabel = computed(() => experimentLabel(autoGroup.experiment.value))\nconst reservedNames = computed(() => engineColumnNames(autoGroup.schemas.value))\n// The example name is shown for pasted names only: CSV and experiment input\n// have columns, not name tokens.\nconst anatomy = computed(() =>\n autoGroup.inputMode.value === 'paste'\n ? engineAnatomy(autoGroup.classes.value, autoGroup.schemas.value, autoGroup.tokenized.value, fieldCards.value)\n : [],\n)\n// A design with no replicate in the names gives groups of one sample. Each\n// field that is on then shows how many groups remain when the user switches\n// it off, so the user can find the field that is not part of the condition.\n// The rule is the one of the summary callout, so the two show together.\nconst fieldHints = computed(() => {\n const needsHelp = mostlySingletons(railGroups.value)\n // ponytail: one full composeGroups for each field that is on (a few ms for\n // 200 samples). Count distinct group keys instead if a large batch is slow.\n return fieldCards.value.fields.map((field, index) => {\n if (!needsHelp || !fieldCards.value.enabled[index]) return null\n const remaining = autoGroup.groupCountWithout(field.key)\n return `off → ${remaining} ${remaining === 1 ? 'group' : 'groups'}`\n })\n})\n\n// The cards describe the batch, not one class, so edits fan out to every class\n// exposing the field — otherwise a multi-class batch (e.g. a Liver class and a\n// Kidney class) ends up with only half its groups reordered.\nfunction onToggleField(cardIndex: number) {\n const field = fieldCards.value.fields[cardIndex]\n if (!field) return\n autoGroup.toggleGroupByField(field.key)\n}\nfunction onMoveField(cardIndex: number, direction: 'up' | 'down') {\n const field = fieldCards.value.fields[cardIndex]\n if (!field) return\n autoGroup.moveGroupByField(field.key, direction)\n}\nfunction onRenameField(cardIndex: number, name: string) {\n const field = fieldCards.value.fields[cardIndex]\n if (!field) return\n autoGroup.renameGroupByField(field.key, name)\n}\nfunction onRouteChange(route: QcRoute) {\n setQcRouting(qcRouteToEngine(route))\n}\nfunction applyAuto() {\n emit('apply', autoGroup.result.value)\n close()\n}\n\n// CSV upload from the Field Recipe header: parse the file into the engine's CSV\n// path. `samples` derives reactively from csvData and `parseInput()` builds the\n// classes/fields/groups; re-apply the current QC routing like the open watcher.\nasync function onCsvFile(file: File) {\n experimentError.value = null\n try {\n // Parse BEFORE mutating engine state so a malformed/empty/header-only CSV\n // (parseCSV throws) leaves the engine untouched instead of half-set.\n const text = await readFileAsText(file)\n const parsed = parseCSV(text)\n autoGroup.csvData.value = parsed\n autoGroup.inputMode.value = 'csv'\n autoGroup.parseInput()\n setQcRouting(qcRouting.value)\n } catch (e) {\n experimentError.value = e instanceof Error ? e.message : 'Could not read that CSV file.'\n }\n}\n\n// Template downloads via the engine. `downloadTemplate(mode, format)` defaults to\n// ('prefilled', 'csv'); pass the explicit mode for each menu action.\nfunction onDownloadTemplate() {\n autoGroup.downloadTemplate('blank', 'csv')\n}\nfunction onDownloadPrefilled() {\n autoGroup.downloadTemplate('prefilled', 'csv')\n}\n\n// ── Manual mode ──────────────────────────────────────────────────────\n// Hex swatches (the SDK's existing manual palette) rather than SmartGroupManual's\n// var(--mint-sample-1) … token story default: SampleSelector tints group colours via hex8 string\n// concatenation (`group.color + '20'`), so an emitted cohort colour must be a\n// concrete hex value — matching the colours the previous manual modal produced.\nconst MANUAL_SWATCH_LABELS = ['Blue', 'Green', 'Amber', 'Red', 'Violet']\nconst manualPalette: ManualPaletteEntry[] = SAMPLE_GROUP_COLOR_OPTIONS.map((c, i) => ({\n c,\n k: MANUAL_SWATCH_LABELS[i] ?? c,\n}))\n// Source from the engine once input is parsed (paste of props.samples, an\n// experiment fetch, OR an uploaded CSV) so CSV-loaded samples reach manual mode\n// too; fall back to the raw prop before the engine has parsed anything.\nconst manualSampleNames = computed(() =>\n autoGroup.samples.value.length > 0 ? autoGroup.samples.value : props.samples,\n)\nconst manualSeed = computed(() => seedFromGroups(props.groups))\n// Remount the manual builder when the sample set changes so it re-seeds from\n// freshly-loaded experiment data rather than a stale snapshot.\nconst manualKey = computed(() => manualSampleNames.value.join('\\n'))\nfunction applyManual(payload: { samples: ManualSample[] }) {\n emit('apply', manualSamplesToResult(payload.samples, props.groups))\n close()\n}\n\nfunction close() {\n emit('update:open', false)\n}\n\n// A close or a newer fetch makes the response of an earlier fetch obsolete.\nlet fetchGeneration = 0\n\nasync function fetchExperimentData() {\n if (!props.experimentId && !props.designData) return\n const generation = ++fetchGeneration\n autoGroup.inputMode.value = 'experiment'\n experimentError.value = null\n experimentLoading.value = true\n try {\n let pluginData: Record<string, unknown> | undefined\n if (props.designData) {\n pluginData = props.designData\n } else {\n const result = await api.get<Record<string, unknown>>(`/experiments/${props.experimentId}/data`)\n pluginData = result.data as Record<string, unknown> | undefined\n if (generation !== fetchGeneration) return\n }\n if (!pluginData || typeof pluginData !== 'object') {\n experimentError.value = 'No design data found for this experiment'\n return\n }\n if (!autoGroup.loadExperimentData(pluginData, { samples: props.samples })) {\n experimentError.value = 'No sample metadata found in this experiment'\n return\n }\n setQcRouting(qcRouting.value)\n } catch (e) {\n if (generation === fetchGeneration) {\n experimentError.value = e instanceof Error ? e.message : 'Failed to fetch experiment data'\n }\n } finally {\n if (generation === fetchGeneration) experimentLoading.value = false\n }\n}\n\n// Populate the engine from props when the modal opens. Sample ingestion still\n// comes from the `samples` / `designData` / `experimentId` props exactly as\n// before — the redesign just drops the multi-step input/schema/merge UI.\nwatch(\n () => props.open,\n (open) => {\n if (!open) {\n fetchGeneration += 1\n experimentLoading.value = false\n return\n }\n activeWorkflow.value = props.initialMode\n experimentError.value = null\n qcAsOverlay.value = true\n excludeQc.value = false\n autoGroup.reset()\n if (props.experimentId || props.designData) {\n fetchExperimentData()\n } else if (props.samples.length > 0) {\n autoGroup.rawText.value = props.samples.join('\\n')\n autoGroup.inputMode.value = 'paste'\n autoGroup.parseInput()\n setQcRouting(qcRouting.value)\n } else {\n autoGroup.inputMode.value = 'paste'\n }\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <BaseModal\n class=\"sg-host\"\n :open=\"open\"\n :closable=\"false\"\n :close-on-overlay=\"false\"\n :close-on-escape=\"false\"\n size=\"lg\"\n @update:open=\"emit('update:open', $event)\"\n >\n <AlertBox v-if=\"experimentError\" tone=\"error\" class=\"sg-host__error\">\n {{ experimentError }}\n </AlertBox>\n\n <SmartGroupFieldRecipe\n v-if=\"activeWorkflow === 'auto'\"\n :fields=\"fieldCards.fields\"\n :enabled=\"fieldCards.enabled\"\n :layers=\"fieldCards.layers\"\n :groups=\"railGroups\"\n :qc-chips=\"qcChips\"\n :route=\"fieldRecipeRoute\"\n :header-count=\"headerCount\"\n :qc-count=\"qcSampleCount\"\n :can-download-template=\"autoGroup.canDownloadTemplate.value\"\n :loading=\"experimentLoading\"\n :experiment-label=\"headerLabel\"\n :anatomy=\"anatomy\"\n :hints=\"fieldHints\"\n :reserved-names=\"reservedNames\"\n renamable\n @toggle=\"onToggleField\"\n @move=\"onMoveField\"\n @rename=\"onRenameField\"\n @update:route=\"onRouteChange\"\n @csv-file=\"onCsvFile\"\n @download-template=\"onDownloadTemplate\"\n @download-prefilled=\"onDownloadPrefilled\"\n @apply=\"applyAuto\"\n @manual=\"activeWorkflow = 'manual'\"\n @cancel=\"close\"\n @close=\"close\"\n />\n <SmartGroupManual\n v-else\n :key=\"manualKey\"\n :sample-names=\"manualSampleNames\"\n :seed=\"manualSeed\"\n :palette=\"manualPalette\"\n @done=\"applyManual\"\n @auto=\"activeWorkflow = 'auto'\"\n @cancel=\"close\"\n @close=\"close\"\n />\n </BaseModal>\n</template>\n","<script setup lang=\"ts\">\n/** Single draggable sample row used by grouped and ungrouped SampleSelector lists. */\nimport BaseCheckbox from './BaseCheckbox.vue'\n\ninterface Props {\n sample: string\n selected: boolean\n dragging?: boolean\n removable?: boolean\n}\n\nwithDefaults(defineProps<Props>(), {\n dragging: false,\n removable: false,\n})\n\nconst emit = defineEmits<{\n toggle: []\n remove: []\n}>()\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-sample-selector__sample',\n dragging ? 'mint-sample-selector__sample--dragging' : '',\n ]\"\n :title=\"sample\"\n :aria-label=\"`Sample: ${sample}`\"\n draggable=\"true\"\n >\n <svg class=\"mint-sample-selector__drag-handle\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8h16M4 16h16\" />\n </svg>\n <BaseCheckbox\n :model-value=\"selected\"\n size=\"sm\"\n :aria-label=\"`Select sample ${sample}`\"\n class=\"mint-sample-selector__checkbox\"\n @update:model-value=\"emit('toggle')\"\n />\n <span class=\"mint-sample-selector__sample-name\">{{ sample }}</span>\n <button\n v-if=\"removable\"\n type=\"button\"\n class=\"mint-sample-selector__remove-btn\"\n title=\"Remove from group\"\n aria-label=\"Remove from group\"\n @click=\"emit('remove')\"\n >\n <svg aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20 12H4\" />\n </svg>\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Single draggable sample row used by grouped and ungrouped SampleSelector lists. */\nimport BaseCheckbox from './BaseCheckbox.vue'\n\ninterface Props {\n sample: string\n selected: boolean\n dragging?: boolean\n removable?: boolean\n}\n\nwithDefaults(defineProps<Props>(), {\n dragging: false,\n removable: false,\n})\n\nconst emit = defineEmits<{\n toggle: []\n remove: []\n}>()\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-sample-selector__sample',\n dragging ? 'mint-sample-selector__sample--dragging' : '',\n ]\"\n :title=\"sample\"\n :aria-label=\"`Sample: ${sample}`\"\n draggable=\"true\"\n >\n <svg class=\"mint-sample-selector__drag-handle\" aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 8h16M4 16h16\" />\n </svg>\n <BaseCheckbox\n :model-value=\"selected\"\n size=\"sm\"\n :aria-label=\"`Select sample ${sample}`\"\n class=\"mint-sample-selector__checkbox\"\n @update:model-value=\"emit('toggle')\"\n />\n <span class=\"mint-sample-selector__sample-name\">{{ sample }}</span>\n <button\n v-if=\"removable\"\n type=\"button\"\n class=\"mint-sample-selector__remove-btn\"\n title=\"Remove from group\"\n aria-label=\"Remove from group\"\n @click=\"emit('remove')\"\n >\n <svg aria-hidden=\"true\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20 12H4\" />\n </svg>\n </button>\n </div>\n</template>\n","import type { SampleGroupNode } from '../composables/useSampleGroups'\nimport type { GroupDragKind, GroupReorderPosition } from './SampleSelector.groups'\n\n/**\n * Handlers and state predicates shared by every level of the group tree.\n * Passed down as a single prop so the recursive row component does not have to\n * re-emit each event at every nesting level.\n */\nexport interface SampleGroupTreeContext {\n isExpanded: (key: string) => boolean\n toggleExpanded: (key: string) => void\n isSampleSelected: (sample: string) => boolean\n isFullySelected: (samples: string[]) => boolean\n isPartiallySelected: (samples: string[]) => boolean\n toggleSamples: (samples: string[]) => void\n toggleSample: (sample: string) => void\n removeGroups: (groupNames: string[]) => void\n removeSampleFromGroup: (sample: string, groupName: string) => void\n openColorPicker: (groupNames: string[], event: Event) => void\n // Drag state + handlers, mirroring `useSampleSelectorDrag`.\n draggingSample: () => string | null\n dragOverGroup: () => string | null\n draggingGroup: () => string | null\n draggingGroupKind: () => GroupDragKind | null\n reorderTarget: () => string | null\n reorderPosition: () => GroupReorderPosition | null\n onSampleDragStart: (sample: string, groupName: string | null, event: DragEvent) => void\n onSampleDragEnd: () => void\n onSampleDragOver: (groupName: string, event: DragEvent) => void\n onSampleDragLeave: () => void\n onSampleDrop: (groupName: string, event: DragEvent) => void\n onGroupDragStart: (name: string, kind: GroupDragKind, event: DragEvent) => void\n onGroupDragEnd: () => void\n onGroupDragOver: (name: string, kind: GroupDragKind, event: DragEvent) => void\n onGroupDragLeave: (event: DragEvent) => void\n onGroupDrop: (name: string, kind: GroupDragKind, event: DragEvent) => void\n /** Alt+ArrowUp / Alt+ArrowDown reorder a group among its siblings. */\n onGroupReorderKeydown: (name: string, kind: GroupDragKind, event: KeyboardEvent) => void\n}\n\n/** Every concrete group name beneath a node, in tree order. */\nexport function collectLeafGroupNames(node: SampleGroupNode): string[] {\n if (node.group) return [node.group.name]\n return node.children.flatMap(collectLeafGroupNames)\n}\n\n/** Expansion keys for a whole tree, used by \"expand all\". */\nexport function collectNodeKeys(nodes: SampleGroupNode[]): string[] {\n return nodes.flatMap(node => [node.key, ...collectNodeKeys(node.children)])\n}\n","<script setup lang=\"ts\">\n/**\n * One row of the sample-selector group hierarchy, rendered recursively so a\n * grouping of any depth (Tissue → Dose → Timepoint → samples) nests properly.\n *\n * Roots carry the family affordances (large dot, colour-family picker) and\n * leaves carry the group affordances (sample drop target, sample rows); the\n * levels in between are pure path nodes that aggregate their descendants.\n */\nimport { computed } from 'vue'\nimport BaseCheckbox from './BaseCheckbox.vue'\nimport SampleSelectorSampleRow from './SampleSelectorSampleRow.vue'\nimport type { SampleGroupNode } from '../composables/useSampleGroups'\nimport { collectLeafGroupNames, type SampleGroupTreeContext } from './SampleSelector.tree'\n\ninterface Props {\n node: SampleGroupNode\n depth: number\n ctx: SampleGroupTreeContext\n}\n\nconst props = defineProps<Props>()\n\nconst isRoot = computed(() => props.depth === 0)\nconst isLeaf = computed(() => props.node.children.length === 0)\nconst expanded = computed(() => props.ctx.isExpanded(props.node.key))\nconst leafGroupNames = computed(() => collectLeafGroupNames(props.node))\nconst selectedCount = computed(() => props.node.allSamples.filter(props.ctx.isSampleSelected).length)\n/** Roots reorder as a family, leaves as individual groups; path nodes stay put. */\nconst dragKind = computed(() => (isRoot.value ? 'major' : isLeaf.value ? 'sub' : null))\n/** Reorder drags address a family by its label but a group by its full name. */\nconst dragName = computed(() =>\n isRoot.value ? props.node.label : (props.node.group?.name ?? props.node.label),\n)\nconst isDragging = computed(\n () => dragKind.value !== null\n && props.ctx.draggingGroup() === dragName.value\n && props.ctx.draggingGroupKind() === dragKind.value,\n)\nconst reorderEdge = computed(() => {\n if (dragKind.value === null) return null\n if (props.ctx.reorderTarget() !== dragName.value) return null\n if (props.ctx.draggingGroupKind() !== dragKind.value) return null\n return props.ctx.reorderPosition()\n})\n\nfunction onHeaderDragStart(event: DragEvent) {\n if (dragKind.value === null) return\n props.ctx.onGroupDragStart(dragName.value, dragKind.value, event)\n}\nfunction onHeaderDragOver(event: DragEvent) {\n if (dragKind.value === null) return\n props.ctx.onGroupDragOver(dragName.value, dragKind.value, event)\n}\nfunction onHeaderDrop(event: DragEvent) {\n if (dragKind.value === null) return\n props.ctx.onGroupDrop(dragName.value, dragKind.value, event)\n}\n\n/** Enter/Space expand the node; Alt+ArrowUp/Down reorder it among its siblings. */\nfunction onHeaderKeydown(event: KeyboardEvent) {\n if (event.altKey) {\n if (dragKind.value === null) return\n props.ctx.onGroupReorderKeydown(dragName.value, dragKind.value, event)\n return\n }\n // Keys from the checkbox or colour button inside the header are theirs, not the header's.\n if (event.target !== event.currentTarget) return\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n props.ctx.toggleExpanded(props.node.key)\n}\n</script>\n\n<template>\n <div\n :class=\"[\n isRoot ? 'mint-sample-selector__major-group' : 'mint-sample-selector__sub-group',\n isLeaf && ctx.dragOverGroup() === node.group?.name\n ? 'mint-sample-selector__sub-group--drag-over'\n : '',\n ]\"\n v-on=\"isLeaf && node.group\n ? {\n dragover: (e: DragEvent) => ctx.onSampleDragOver(node.group!.name, e),\n dragleave: ctx.onSampleDragLeave,\n drop: (e: DragEvent) => ctx.onSampleDrop(node.group!.name, e),\n }\n : {}\"\n >\n <!-- Header -->\n <div\n :class=\"[\n isRoot ? 'mint-sample-selector__major-header' : 'mint-sample-selector__sub-header',\n isDragging ? 'mint-sample-selector__header--dragging' : '',\n reorderEdge === 'before' ? 'mint-sample-selector__header--drag-over-before' : '',\n reorderEdge === 'after' ? 'mint-sample-selector__header--drag-over-after' : '',\n ]\"\n :draggable=\"dragKind !== null\"\n role=\"button\"\n tabindex=\"0\"\n :aria-expanded=\"expanded\"\n :aria-label=\"dragKind !== null\n ? `${node.label}. Press Alt with the up or down arrow keys to reorder.`\n : node.label\"\n @click=\"ctx.toggleExpanded(node.key)\"\n @keydown=\"onHeaderKeydown\"\n @dragstart=\"onHeaderDragStart\"\n @dragend=\"ctx.onGroupDragEnd\"\n @dragover=\"onHeaderDragOver\"\n @dragleave=\"ctx.onGroupDragLeave($event)\"\n @drop=\"onHeaderDrop\"\n >\n <svg\n :class=\"[\n 'mint-sample-selector__chevron',\n isRoot ? '' : 'mint-sample-selector__chevron--small',\n expanded ? 'mint-sample-selector__chevron--open' : '',\n ]\"\n fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n\n <BaseCheckbox\n :model-value=\"ctx.isFullySelected(node.allSamples)\"\n :indeterminate=\"ctx.isPartiallySelected(node.allSamples)\"\n size=\"sm\"\n :aria-label=\"`Select ${node.label}`\"\n class=\"mint-sample-selector__checkbox\"\n @click.stop\n @update:model-value=\"ctx.toggleSamples(node.allSamples)\"\n />\n\n <button\n v-if=\"isRoot\"\n type=\"button\"\n class=\"mint-sample-selector__color-dot mint-sample-selector__color-dot--large mint-sample-selector__color-dot--clickable\"\n :style=\"{ backgroundColor: node.color }\"\n :title=\"`Change ${node.label} color family`\"\n :aria-label=\"`Change ${node.label} color family`\"\n @click.stop=\"ctx.openColorPicker(leafGroupNames, $event)\"\n />\n <div\n v-else\n class=\"mint-sample-selector__color-dot\"\n :style=\"{ backgroundColor: node.color }\"\n aria-hidden=\"true\"\n />\n\n <span\n :class=\"isRoot ? 'mint-sample-selector__major-name' : 'mint-sample-selector__sub-name'\"\n :title=\"node.group?.name ?? node.label\"\n >{{ node.label }}</span>\n\n <span class=\"mint-sample-selector__count-badge\">{{ selectedCount }}/{{ node.allSamples.length }}</span>\n\n <button\n type=\"button\"\n class=\"mint-sample-selector__delete-btn mint-sample-selector__delete-btn--hidden\"\n :title=\"`Remove ${node.label}`\"\n :aria-label=\"`Remove ${node.label}`\"\n @click.stop=\"ctx.removeGroups(leafGroupNames)\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- Children: nested nodes, or the sample rows on a leaf -->\n <Transition name=\"mint-collapse\">\n <div\n v-if=\"expanded && !isLeaf\"\n class=\"mint-sample-selector__sub-groups\"\n :style=\"{ borderColor: node.displayBorder }\"\n >\n <SampleSelectorGroupNode\n v-for=\"child in node.children\"\n :key=\"child.key\"\n :node=\"child\"\n :depth=\"depth + 1\"\n :ctx=\"ctx\"\n />\n </div>\n <div\n v-else-if=\"expanded && node.group\"\n class=\"mint-sample-selector__samples\"\n :style=\"{ borderColor: node.displayBorder }\"\n >\n <SampleSelectorSampleRow\n v-for=\"sample in node.group.samples\"\n :key=\"sample\"\n :sample=\"sample\"\n :selected=\"ctx.isSampleSelected(sample)\"\n :dragging=\"ctx.draggingSample() === sample\"\n removable\n @dragstart=\"(e: DragEvent) => ctx.onSampleDragStart(sample, node.group!.name, e)\"\n @dragend=\"ctx.onSampleDragEnd\"\n @toggle=\"ctx.toggleSample(sample)\"\n @remove=\"ctx.removeSampleFromGroup(sample, node.group!.name)\"\n />\n </div>\n </Transition>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * One row of the sample-selector group hierarchy, rendered recursively so a\n * grouping of any depth (Tissue → Dose → Timepoint → samples) nests properly.\n *\n * Roots carry the family affordances (large dot, colour-family picker) and\n * leaves carry the group affordances (sample drop target, sample rows); the\n * levels in between are pure path nodes that aggregate their descendants.\n */\nimport { computed } from 'vue'\nimport BaseCheckbox from './BaseCheckbox.vue'\nimport SampleSelectorSampleRow from './SampleSelectorSampleRow.vue'\nimport type { SampleGroupNode } from '../composables/useSampleGroups'\nimport { collectLeafGroupNames, type SampleGroupTreeContext } from './SampleSelector.tree'\n\ninterface Props {\n node: SampleGroupNode\n depth: number\n ctx: SampleGroupTreeContext\n}\n\nconst props = defineProps<Props>()\n\nconst isRoot = computed(() => props.depth === 0)\nconst isLeaf = computed(() => props.node.children.length === 0)\nconst expanded = computed(() => props.ctx.isExpanded(props.node.key))\nconst leafGroupNames = computed(() => collectLeafGroupNames(props.node))\nconst selectedCount = computed(() => props.node.allSamples.filter(props.ctx.isSampleSelected).length)\n/** Roots reorder as a family, leaves as individual groups; path nodes stay put. */\nconst dragKind = computed(() => (isRoot.value ? 'major' : isLeaf.value ? 'sub' : null))\n/** Reorder drags address a family by its label but a group by its full name. */\nconst dragName = computed(() =>\n isRoot.value ? props.node.label : (props.node.group?.name ?? props.node.label),\n)\nconst isDragging = computed(\n () => dragKind.value !== null\n && props.ctx.draggingGroup() === dragName.value\n && props.ctx.draggingGroupKind() === dragKind.value,\n)\nconst reorderEdge = computed(() => {\n if (dragKind.value === null) return null\n if (props.ctx.reorderTarget() !== dragName.value) return null\n if (props.ctx.draggingGroupKind() !== dragKind.value) return null\n return props.ctx.reorderPosition()\n})\n\nfunction onHeaderDragStart(event: DragEvent) {\n if (dragKind.value === null) return\n props.ctx.onGroupDragStart(dragName.value, dragKind.value, event)\n}\nfunction onHeaderDragOver(event: DragEvent) {\n if (dragKind.value === null) return\n props.ctx.onGroupDragOver(dragName.value, dragKind.value, event)\n}\nfunction onHeaderDrop(event: DragEvent) {\n if (dragKind.value === null) return\n props.ctx.onGroupDrop(dragName.value, dragKind.value, event)\n}\n\n/** Enter/Space expand the node; Alt+ArrowUp/Down reorder it among its siblings. */\nfunction onHeaderKeydown(event: KeyboardEvent) {\n if (event.altKey) {\n if (dragKind.value === null) return\n props.ctx.onGroupReorderKeydown(dragName.value, dragKind.value, event)\n return\n }\n // Keys from the checkbox or colour button inside the header are theirs, not the header's.\n if (event.target !== event.currentTarget) return\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n props.ctx.toggleExpanded(props.node.key)\n}\n</script>\n\n<template>\n <div\n :class=\"[\n isRoot ? 'mint-sample-selector__major-group' : 'mint-sample-selector__sub-group',\n isLeaf && ctx.dragOverGroup() === node.group?.name\n ? 'mint-sample-selector__sub-group--drag-over'\n : '',\n ]\"\n v-on=\"isLeaf && node.group\n ? {\n dragover: (e: DragEvent) => ctx.onSampleDragOver(node.group!.name, e),\n dragleave: ctx.onSampleDragLeave,\n drop: (e: DragEvent) => ctx.onSampleDrop(node.group!.name, e),\n }\n : {}\"\n >\n <!-- Header -->\n <div\n :class=\"[\n isRoot ? 'mint-sample-selector__major-header' : 'mint-sample-selector__sub-header',\n isDragging ? 'mint-sample-selector__header--dragging' : '',\n reorderEdge === 'before' ? 'mint-sample-selector__header--drag-over-before' : '',\n reorderEdge === 'after' ? 'mint-sample-selector__header--drag-over-after' : '',\n ]\"\n :draggable=\"dragKind !== null\"\n role=\"button\"\n tabindex=\"0\"\n :aria-expanded=\"expanded\"\n :aria-label=\"dragKind !== null\n ? `${node.label}. Press Alt with the up or down arrow keys to reorder.`\n : node.label\"\n @click=\"ctx.toggleExpanded(node.key)\"\n @keydown=\"onHeaderKeydown\"\n @dragstart=\"onHeaderDragStart\"\n @dragend=\"ctx.onGroupDragEnd\"\n @dragover=\"onHeaderDragOver\"\n @dragleave=\"ctx.onGroupDragLeave($event)\"\n @drop=\"onHeaderDrop\"\n >\n <svg\n :class=\"[\n 'mint-sample-selector__chevron',\n isRoot ? '' : 'mint-sample-selector__chevron--small',\n expanded ? 'mint-sample-selector__chevron--open' : '',\n ]\"\n fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n\n <BaseCheckbox\n :model-value=\"ctx.isFullySelected(node.allSamples)\"\n :indeterminate=\"ctx.isPartiallySelected(node.allSamples)\"\n size=\"sm\"\n :aria-label=\"`Select ${node.label}`\"\n class=\"mint-sample-selector__checkbox\"\n @click.stop\n @update:model-value=\"ctx.toggleSamples(node.allSamples)\"\n />\n\n <button\n v-if=\"isRoot\"\n type=\"button\"\n class=\"mint-sample-selector__color-dot mint-sample-selector__color-dot--large mint-sample-selector__color-dot--clickable\"\n :style=\"{ backgroundColor: node.color }\"\n :title=\"`Change ${node.label} color family`\"\n :aria-label=\"`Change ${node.label} color family`\"\n @click.stop=\"ctx.openColorPicker(leafGroupNames, $event)\"\n />\n <div\n v-else\n class=\"mint-sample-selector__color-dot\"\n :style=\"{ backgroundColor: node.color }\"\n aria-hidden=\"true\"\n />\n\n <span\n :class=\"isRoot ? 'mint-sample-selector__major-name' : 'mint-sample-selector__sub-name'\"\n :title=\"node.group?.name ?? node.label\"\n >{{ node.label }}</span>\n\n <span class=\"mint-sample-selector__count-badge\">{{ selectedCount }}/{{ node.allSamples.length }}</span>\n\n <button\n type=\"button\"\n class=\"mint-sample-selector__delete-btn mint-sample-selector__delete-btn--hidden\"\n :title=\"`Remove ${node.label}`\"\n :aria-label=\"`Remove ${node.label}`\"\n @click.stop=\"ctx.removeGroups(leafGroupNames)\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- Children: nested nodes, or the sample rows on a leaf -->\n <Transition name=\"mint-collapse\">\n <div\n v-if=\"expanded && !isLeaf\"\n class=\"mint-sample-selector__sub-groups\"\n :style=\"{ borderColor: node.displayBorder }\"\n >\n <SampleSelectorGroupNode\n v-for=\"child in node.children\"\n :key=\"child.key\"\n :node=\"child\"\n :depth=\"depth + 1\"\n :ctx=\"ctx\"\n />\n </div>\n <div\n v-else-if=\"expanded && node.group\"\n class=\"mint-sample-selector__samples\"\n :style=\"{ borderColor: node.displayBorder }\"\n >\n <SampleSelectorSampleRow\n v-for=\"sample in node.group.samples\"\n :key=\"sample\"\n :sample=\"sample\"\n :selected=\"ctx.isSampleSelected(sample)\"\n :dragging=\"ctx.draggingSample() === sample\"\n removable\n @dragstart=\"(e: DragEvent) => ctx.onSampleDragStart(sample, node.group!.name, e)\"\n @dragend=\"ctx.onSampleDragEnd\"\n @toggle=\"ctx.toggleSample(sample)\"\n @remove=\"ctx.removeSampleFromGroup(sample, node.group!.name)\"\n />\n </div>\n </Transition>\n </div>\n</template>\n","/** Split a group name into normalized hierarchy segments. */\nexport function splitSampleGroupPath(groupName: string, separator: string): string[] {\n const parts = groupName\n .split(separator)\n .map(part => part.trim())\n .filter(part => part.length > 0)\n return parts.length > 0 ? parts : [groupName]\n}\n\n/** First normalized hierarchy segment, used as the reorderable family key. */\nexport function getSampleGroupMajorPrefix(groupName: string, separator: string): string {\n return splitSampleGroupPath(groupName, separator)[0]\n}\n\n/** Normalized path above a concrete leaf group. */\nexport function getSampleGroupParentPath(groupName: string, separator: string): string {\n const parts = splitSampleGroupPath(groupName, separator)\n return parts.length > 1 ? parts.slice(0, -1).join(separator) : ''\n}\n","import { computed, toValue, type ComputedRef, type Ref } from 'vue'\nimport type { SampleGroup } from '../types'\nimport { deriveShade } from '../utils/color'\nimport { splitSampleGroupPath } from '../utils/sampleGroupPath'\n\nexport type SampleGroupSource<T> =\n | T\n | Ref<T>\n | ComputedRef<T>\n | (() => T)\n\nexport interface DisplaySampleSubGroup extends SampleGroup {\n displayColor: string\n displayBg: string\n displayBorder: string\n}\n\nexport interface SampleMajorGroup {\n name: string\n color: string\n subGroups: DisplaySampleSubGroup[]\n allSamples: string[]\n}\n\n/**\n * One node of the sample-group display hierarchy. Interior nodes are path\n * prefixes of the group names (`Liver`, `Liver / High`); leaves carry the\n * concrete group. A group's `name` stays the full path on its leaf because\n * every mutation — drag, delete, colour, selection — resolves groups by name.\n */\nexport interface SampleGroupNode {\n /** Unique across the tree; use for `:key` and expansion state. */\n key: string\n /** This node's own path segment, not the full path. */\n label: string\n color: string\n displayBg: string\n displayBorder: string\n /** Every sample beneath this node, in group order. */\n allSamples: string[]\n /** Empty for leaves. */\n children: SampleGroupNode[]\n /** Present only on leaves. */\n group?: DisplaySampleSubGroup\n}\n\nexport interface UseSampleGroupsOptions {\n groups: SampleGroupSource<readonly SampleGroup[]>\n samples?: SampleGroupSource<readonly string[]>\n separator?: SampleGroupSource<string | null | undefined>\n fallbackColor?: SampleGroupSource<string | null | undefined>\n}\n\nexport interface UseSampleGroupsReturn {\n groups: ComputedRef<SampleGroup[]>\n separator: ComputedRef<string>\n /** Full N-layer display hierarchy; one level per group-name segment. */\n groupTree: ComputedRef<SampleGroupNode[]>\n /** Deepest layer count across the tree (1 when groups are flat). */\n maxDepth: ComputedRef<number>\n hierarchicalGroups: ComputedRef<SampleMajorGroup[]>\n showHierarchy: ComputedRef<boolean>\n groupedSamples: ComputedRef<Set<string>>\n ungroupedSamples: ComputedRef<string[]>\n findGroup: (groupName: string) => SampleGroup | undefined\n getGroupColor: (groupName: string) => string\n}\n\n/** Shared sample-group hierarchy, color, and ungrouped-sample derivation for lab selectors. */\nexport function useSampleGroups(options: UseSampleGroupsOptions): UseSampleGroupsReturn {\n const groups = computed(() => [...toValue(options.groups)])\n const samples = computed(() => [...(toValue(options.samples) ?? [])])\n const fallbackColor = computed(() => toValue(options.fallbackColor) || '#3B82F6')\n const separator = computed(() => {\n const explicit = toValue(options.separator)\n if (explicit) return explicit\n return groups.value.some(group => group.name.includes('/')) ? '/' : '_'\n })\n\n // Groups are bucketed by their first segment (the major/family), then each\n // family is expanded into a full prefix tree. Shades are assigned per family\n // across its leaves so a family keeps one hue with a light→dark gradient,\n // exactly as the two-level model did.\n const families = computed(() => {\n const byMajor = new Map<string, SampleGroup[]>()\n for (const group of groups.value) {\n const majorName = getMajorGroupName(group.name, separator.value)\n const existing = byMajor.get(majorName)\n if (existing) existing.push(group)\n else byMajor.set(majorName, [group])\n }\n return [...byMajor.entries()].map(([name, members]) => {\n const color = members[0]?.color || fallbackColor.value\n return {\n name,\n color,\n subGroups: members.map((group, index) => {\n const displayColor = deriveShade(color, index, members.length)\n return {\n ...group,\n displayColor,\n displayBg: displayColor + '20',\n displayBorder: displayColor + '40',\n }\n }),\n }\n })\n })\n\n const groupTree = computed<SampleGroupNode[]>(() => {\n const roots: SampleGroupNode[] = []\n\n for (const family of families.value) {\n // Interior nodes are shared by path so siblings collapse into one parent;\n // leaves are keyed separately so a group whose full path equals an\n // interior path (e.g. \"Liver\" alongside \"Liver / High\") stays reachable\n // instead of being swallowed by the prefix node.\n const interior = new Map<string, SampleGroupNode>()\n\n for (const subGroup of family.subGroups) {\n const segments = splitSampleGroupPath(subGroup.name, separator.value)\n const leafLabel = segments[segments.length - 1] ?? subGroup.name\n let siblings = roots\n let path = ''\n\n for (const segment of segments.slice(0, -1)) {\n path = path ? `${path}/${segment}` : segment\n let node = interior.get(path)\n if (!node) {\n node = {\n key: `node:${family.name}/${path}`,\n label: segment,\n color: family.color,\n displayBg: family.color + '20',\n displayBorder: family.color + '40',\n allSamples: [],\n children: [],\n }\n interior.set(path, node)\n siblings.push(node)\n }\n node.allSamples.push(...subGroup.samples)\n siblings = node.children\n }\n\n siblings.push({\n key: `leaf:${subGroup.name}`,\n label: leafLabel,\n color: subGroup.displayColor,\n displayBg: subGroup.displayBg,\n displayBorder: subGroup.displayBorder,\n allSamples: [...subGroup.samples],\n children: [],\n group: subGroup,\n })\n }\n }\n\n return roots\n })\n\n const maxDepth = computed(() => {\n const depthOf = (nodes: SampleGroupNode[]): number =>\n nodes.reduce((deepest, node) => Math.max(deepest, 1 + depthOf(node.children)), 0)\n return depthOf(groupTree.value)\n })\n\n const hierarchicalGroups = computed<SampleMajorGroup[]>(() =>\n families.value.map(family => ({\n name: family.name,\n color: family.color,\n subGroups: family.subGroups,\n allSamples: family.subGroups.flatMap(group => group.samples),\n })),\n )\n\n const showHierarchy = computed(() => maxDepth.value > 1)\n\n const groupedSamples = computed(() =>\n new Set(groups.value.flatMap(group => group.samples))\n )\n\n const ungroupedSamples = computed(() =>\n samples.value.filter(sample => !groupedSamples.value.has(sample))\n )\n\n function findGroup(groupName: string): SampleGroup | undefined {\n return groups.value.find(group => group.name === groupName)\n }\n\n function getGroupColor(groupName: string): string {\n return findGroup(groupName)?.color || fallbackColor.value\n }\n\n return {\n groups,\n separator,\n groupTree,\n maxDepth,\n hierarchicalGroups,\n showHierarchy,\n groupedSamples,\n ungroupedSamples,\n findGroup,\n getGroupColor,\n }\n}\n\nfunction getMajorGroupName(groupName: string, separator: string): string {\n return splitSampleGroupPath(groupName, separator)[0]\n}\n","import type { SampleGroup } from '../types'\nimport { getSampleGroupMajorPrefix } from '../utils/sampleGroupPath'\n\nexport {\n getSampleGroupMajorPrefix,\n getSampleGroupParentPath,\n splitSampleGroupPath,\n} from '../utils/sampleGroupPath'\n\nexport type GroupDragKind = 'major' | 'sub' | 'flat'\nexport type GroupReorderPosition = 'before' | 'after'\n\nexport function detectSampleGroupSeparator(groups: SampleGroup[]): string {\n return groups.some(group => group.name.includes('/')) ? '/' : '_'\n}\n\nexport function moveSampleToGroup(\n groups: SampleGroup[],\n sample: string,\n sourceGroup: string | null,\n targetGroup: string,\n): SampleGroup[] {\n if (sourceGroup === targetGroup) return groups\n\n return groups.map(group => {\n if (sourceGroup && group.name === sourceGroup) {\n return { ...group, samples: group.samples.filter(item => item !== sample) }\n }\n if (group.name === targetGroup && !group.samples.includes(sample)) {\n return { ...group, samples: [...group.samples, sample] }\n }\n return group\n })\n}\n\n/**\n * Drop every named group. Callers pass the concrete group names beneath the\n * node being removed, so one helper covers a single group, a whole family, or\n * any intermediate hierarchy level.\n */\nexport function removeSampleGroups(groups: SampleGroup[], groupNames: string[]): SampleGroup[] {\n const removing = new Set(groupNames)\n return groups.filter(group => !removing.has(group.name))\n}\n\nexport function removeSampleFromGroup(\n groups: SampleGroup[],\n sample: string,\n groupName: string,\n): SampleGroup[] {\n return groups.map(group =>\n group.name === groupName\n ? { ...group, samples: group.samples.filter(item => item !== sample) }\n : group,\n )\n}\n\nexport function reorderSampleGroup(\n groups: SampleGroup[],\n source: string,\n target: string,\n position: GroupReorderPosition,\n): SampleGroup[] {\n if (source === target) return groups\n\n const nextGroups = [...groups]\n const sourceIndex = nextGroups.findIndex(group => group.name === source)\n if (sourceIndex === -1) return groups\n\n const [item] = nextGroups.splice(sourceIndex, 1)\n const targetIndex = nextGroups.findIndex(group => group.name === target)\n if (targetIndex === -1) return groups\n\n nextGroups.splice(position === 'before' ? targetIndex : targetIndex + 1, 0, item)\n return nextGroups\n}\n\nexport function reorderSampleMajorGroup(\n groups: SampleGroup[],\n source: string,\n target: string,\n position: GroupReorderPosition,\n): SampleGroup[] {\n if (source === target) return groups\n\n const separator = detectSampleGroupSeparator(groups)\n const isSource = (group: SampleGroup) => getSampleGroupMajorPrefix(group.name, separator) === source\n const isTarget = (group: SampleGroup) => getSampleGroupMajorPrefix(group.name, separator) === target\n\n const sourceGroups = groups.filter(isSource)\n if (sourceGroups.length === 0) return groups\n\n const remainingGroups = groups.filter(group => !isSource(group))\n let firstTargetIndex = -1\n let lastTargetIndex = -1\n\n remainingGroups.forEach((group, index) => {\n if (isTarget(group)) {\n if (firstTargetIndex === -1) firstTargetIndex = index\n lastTargetIndex = index\n }\n })\n\n if (firstTargetIndex === -1) return groups\n\n const insertIndex = position === 'before' ? firstTargetIndex : lastTargetIndex + 1\n remainingGroups.splice(insertIndex, 0, ...sourceGroups)\n return remainingGroups\n}\n","import { ref } from 'vue'\nimport type { SampleGroup } from '../types'\nimport { useReorderAnnouncer } from '../composables/useListReorder'\nimport { getSampleGroupMajorPrefix } from '../utils/sampleGroupPath'\nimport {\n detectSampleGroupSeparator,\n getSampleGroupParentPath,\n moveSampleToGroup,\n reorderSampleGroup,\n reorderSampleMajorGroup,\n type GroupDragKind,\n type GroupReorderPosition,\n} from './SampleSelector.groups'\n\ninterface MutableSampleGroups {\n value: SampleGroup[]\n}\n\nexport function useSampleSelectorDrag(groups: MutableSampleGroups) {\n const draggingSample = ref<string | null>(null)\n const dragSourceGroup = ref<string | null>(null)\n const dragOverGroup = ref<string | null>(null)\n\n const draggingGroup = ref<string | null>(null)\n const draggingGroupKind = ref<GroupDragKind | null>(null)\n const reorderTarget = ref<string | null>(null)\n const reorderPosition = ref<GroupReorderPosition | null>(null)\n const { announcement, announce, announcerStyle } = useReorderAnnouncer()\n\n function handleDragStart(sample: string, sourceGroup: string | null, event: DragEvent) {\n draggingSample.value = sample\n dragSourceGroup.value = sourceGroup\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', sample)\n }\n }\n\n function resetDragState() {\n draggingSample.value = null\n dragSourceGroup.value = null\n dragOverGroup.value = null\n }\n\n function handleDragEnd() {\n resetDragState()\n }\n\n function handleDragOver(groupName: string, event: DragEvent) {\n // Only accept this drop target when a sample is being dragged; group drags are handled separately.\n if (!draggingSample.value) return\n event.preventDefault()\n if (event.dataTransfer) {\n event.dataTransfer.dropEffect = 'move'\n }\n dragOverGroup.value = groupName\n }\n\n function handleDragLeave() {\n dragOverGroup.value = null\n }\n\n function handleDrop(targetGroupName: string, event: DragEvent) {\n if (!draggingSample.value) return\n event.preventDefault()\n\n const sample = draggingSample.value\n const sourceGroup = dragSourceGroup.value\n\n if (sourceGroup === targetGroupName) {\n resetDragState()\n return\n }\n\n groups.value = moveSampleToGroup(groups.value, sample, sourceGroup, targetGroupName)\n resetDragState()\n }\n\n function handleGroupDragStart(name: string, kind: GroupDragKind, event: DragEvent) {\n draggingGroup.value = name\n draggingGroupKind.value = kind\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', `mint-group:${name}`)\n }\n }\n\n function handleGroupDragEnd() {\n draggingGroup.value = null\n draggingGroupKind.value = null\n reorderTarget.value = null\n reorderPosition.value = null\n }\n\n function handleGroupDragOver(name: string, kind: GroupDragKind, event: DragEvent) {\n if (!draggingGroup.value || draggingGroupKind.value !== kind) return\n if (draggingGroup.value === name) return\n\n // Leaf reorder is restricted to siblings under the same immediate parent;\n // crossing any hierarchy boundary would silently rename the group.\n if (kind === 'sub') {\n const separator = detectSampleGroupSeparator(groups.value)\n if (\n getSampleGroupParentPath(draggingGroup.value, separator) !==\n getSampleGroupParentPath(name, separator)\n ) return\n }\n\n event.preventDefault()\n if (event.dataTransfer) event.dataTransfer.dropEffect = 'move'\n\n const rect = (event.currentTarget as HTMLElement).getBoundingClientRect()\n reorderTarget.value = name\n reorderPosition.value = event.clientY < rect.top + rect.height / 2 ? 'before' : 'after'\n }\n\n function handleGroupDragLeave(event: DragEvent) {\n const cur = event.currentTarget as HTMLElement\n const rel = event.relatedTarget as Node | null\n if (rel && cur.contains(rel)) return\n reorderTarget.value = null\n reorderPosition.value = null\n }\n\n function handleGroupDrop(name: string, kind: GroupDragKind, event: DragEvent) {\n if (!draggingGroup.value || draggingGroupKind.value !== kind) {\n handleGroupDragEnd()\n return\n }\n const position = reorderPosition.value\n if (!position || draggingGroup.value === name) {\n handleGroupDragEnd()\n return\n }\n\n event.preventDefault()\n\n if (kind === 'major') {\n const nextGroups = reorderSampleMajorGroup(groups.value, draggingGroup.value, name, position)\n if (nextGroups !== groups.value) groups.value = nextGroups\n } else {\n const nextGroups = reorderSampleGroup(groups.value, draggingGroup.value, name, position)\n if (nextGroups !== groups.value) groups.value = nextGroups\n }\n\n handleGroupDragEnd()\n }\n\n /**\n * Keyboard alternative to the group reorder drag. The hierarchy constrains a\n * move to the dragged node's siblings (a leaf may not cross a parent), so this\n * is not a flat list reorder and does not use `useListReorder`: Alt+ArrowUp /\n * Alt+ArrowDown swap the node with its previous / next sibling instead.\n */\n function handleGroupReorderKeydown(name: string, kind: GroupDragKind, event: KeyboardEvent) {\n if (!event.altKey) return\n const direction = event.key === 'ArrowUp' ? -1 : event.key === 'ArrowDown' ? 1 : 0\n if (direction === 0) return\n\n event.preventDefault()\n const separator = detectSampleGroupSeparator(groups.value)\n const siblings = kind === 'major'\n ? uniqueMajorPrefixes(groups.value, separator)\n : groups.value\n .filter(group =>\n getSampleGroupParentPath(group.name, separator) ===\n getSampleGroupParentPath(name, separator))\n .map(group => group.name)\n\n const currentIndex = siblings.indexOf(name)\n const target = siblings[currentIndex + direction]\n if (currentIndex === -1 || target === undefined) return\n\n const position: GroupReorderPosition = direction === -1 ? 'before' : 'after'\n const nextGroups = kind === 'major'\n ? reorderSampleMajorGroup(groups.value, name, target, position)\n : reorderSampleGroup(groups.value, name, target, position)\n\n if (nextGroups === groups.value) return\n groups.value = nextGroups\n announce(`${name} moved ${direction === -1 ? 'up' : 'down'} to position ${currentIndex + direction + 1} of ${siblings.length}.`)\n }\n\n return {\n announcement,\n announcerStyle,\n handleGroupReorderKeydown,\n draggingSample,\n dragOverGroup,\n draggingGroup,\n draggingGroupKind,\n reorderTarget,\n reorderPosition,\n handleDragStart,\n handleDragEnd,\n handleDragOver,\n handleDragLeave,\n handleDrop,\n handleGroupDragStart,\n handleGroupDragEnd,\n handleGroupDragOver,\n handleGroupDragLeave,\n handleGroupDrop,\n }\n}\n\n/** Major-group prefixes in tree order, de-duplicated. */\nfunction uniqueMajorPrefixes(groups: SampleGroup[], separator: string): string[] {\n const seen: string[] = []\n for (const group of groups) {\n const prefix = getSampleGroupMajorPrefix(group.name, separator)\n if (!seen.includes(prefix)) seen.push(prefix)\n }\n return seen\n}\n","import type { ComputedRef } from 'vue'\nimport { useListSelection } from '../composables/useListSelection'\n\nexport interface UseSampleSelectorSelectionOptions {\n selected: () => string[]\n samples: () => string[]\n emitSelected: (samples: string[]) => void\n}\n\nexport interface UseSampleSelectorSelectionReturn {\n isAllSelected: ComputedRef<boolean>\n toggleSelectAll: () => void\n toggleSample: (sample: string) => void\n toggleSamplesSelection: (samples: string[]) => void\n isFullySelected: (samples: string[]) => boolean\n isPartiallySelected: (samples: string[]) => boolean\n}\n\n/**\n * Selection state for the sample selector. Works on plain sample arrays rather\n * than group names, so any level of the group hierarchy can drive it by handing\n * over the samples beneath that node.\n */\nexport function useSampleSelectorSelection(\n options: UseSampleSelectorSelectionOptions,\n): UseSampleSelectorSelectionReturn {\n const sampleSelection = useListSelection({\n selected: options.selected,\n items: options.samples,\n })\n\n function emitSelection(samples: string[]) {\n options.emitSelected(samples)\n }\n\n function toggleSelectAll() {\n emitSelection(sampleSelection.toggleAll())\n }\n\n function toggleSample(sample: string) {\n emitSelection(sampleSelection.toggleValue(sample))\n }\n\n function toggleSamplesSelection(samples: string[]) {\n emitSelection(sampleSelection.toggleValues(samples))\n }\n\n function isFullySelected(samples: string[]): boolean {\n return sampleSelection.isFullySelected(samples)\n }\n\n function isPartiallySelected(samples: string[]): boolean {\n return sampleSelection.isPartiallySelected(samples)\n }\n\n return {\n isAllSelected: sampleSelection.isAllSelected,\n toggleSelectAll,\n toggleSample,\n toggleSamplesSelection,\n isFullySelected,\n isPartiallySelected,\n }\n}\n","<script setup lang=\"ts\">\n/** Interactive sample list with drag-and-drop group assignment, color labeling, and smart-group support. */\nimport { ref, computed } from 'vue'\nimport { Layers, RotateCcw, WandSparkles } from 'lucide-vue-next'\nimport BaseButton from './BaseButton.vue'\nimport BaseCheckbox from './BaseCheckbox.vue'\nimport BaseInput from './BaseInput.vue'\nimport AutoGroupModalInternal from './internal/AutoGroupModalInternal.vue'\nimport SampleSelectorSampleRow from './SampleSelectorSampleRow.vue'\nimport SampleSelectorGroupNode from './SampleSelectorGroupNode.vue'\nimport { collectNodeKeys, type SampleGroupTreeContext } from './SampleSelector.tree'\nimport type { SampleGroup } from '../types'\nimport type { AutoGroupResult } from '../types/auto-group'\nimport { useTextSearch } from '../composables/useTextSearch'\nimport { useSampleGroups } from '../composables/useSampleGroups'\nimport { useExpansionSet } from '../composables/useExpansionSet'\nimport { useExperimentSamples } from '../composables/useExperimentSamples'\nimport { useSampleSelectorDrag } from './SampleSelector.drag'\nimport { useSampleSelectorSelection } from './SampleSelector.selection'\nimport {\n applySampleGroupColorEdit,\n createSampleGroup,\n getSampleGroupColorEditSeed,\n type ColorEdit,\n} from './SampleSelector.colors'\nimport {\n removeSampleFromGroup as removeSampleFromSampleGroup,\n removeSampleGroups,\n} from './SampleSelector.groups'\nimport DataStateInternal from './internal/DataStateInternal.vue'\n\ninterface Props {\n samples?: string[]\n modelValue: string[]\n groups?: SampleGroup[]\n enableGrouping?: boolean\n enableSmartGroup?: boolean\n experimentId?: number\n designData?: Record<string, unknown>\n autoloadExperimentData?: boolean\n /** Replace the selector body with a loading placeholder. */\n loading?: boolean\n /**\n * Error message. When set, the selector body is replaced by an error state.\n * `null`/`undefined` renders normally.\n */\n error?: string | null\n /** Empty-state headline shown when there are no samples to select from. */\n emptyText?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n samples: () => [],\n groups: () => [],\n enableGrouping: true,\n enableSmartGroup: true,\n experimentId: undefined,\n designData: undefined,\n autoloadExperimentData: true,\n loading: false,\n error: null,\n emptyText: 'No samples available',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [samples: string[]]\n 'update:groups': [groups: SampleGroup[]]\n smartGroup: [result: AutoGroupResult]\n}>()\n\n// UI State\nconst showSmartGroupModal = ref(false)\nconst groupingModalMode = ref<'auto' | 'manual'>('auto')\nconst newGroupName = ref('')\nconst editingColor = ref<ColorEdit | null>(null)\nconst colorPickerInput = ref<HTMLInputElement | null>(null)\nconst searchQuery = ref('')\nconst groupExpansion = useExpansionSet()\n\n// Computed: groups from props\nconst internalGroups = computed({\n get: () => props.groups,\n set: (value) => emit('update:groups', value),\n})\n\nconst {\n announcement: reorderAnnouncement,\n announcerStyle,\n draggingSample,\n dragOverGroup,\n draggingGroup,\n draggingGroupKind,\n reorderTarget,\n reorderPosition,\n handleDragStart,\n handleDragEnd,\n handleDragOver,\n handleDragLeave,\n handleDrop,\n handleGroupDragStart,\n handleGroupReorderKeydown,\n handleGroupDragEnd,\n handleGroupDragOver,\n handleGroupDragLeave,\n handleGroupDrop,\n} = useSampleSelectorDrag(internalGroups)\n\nconst providedSamples = computed(() => props.samples)\nconst shouldLoadExperimentSamples = computed(() =>\n props.autoloadExperimentData && providedSamples.value.length === 0,\n)\nconst experimentSamples = useExperimentSamples({\n experimentId: () => props.experimentId,\n designData: () => props.designData,\n enabled: shouldLoadExperimentSamples,\n})\nconst resolvedSamples = computed(() =>\n providedSamples.value.length > 0 ? providedSamples.value : experimentSamples.samples.value,\n)\nconst resolvedExperimentId = computed(() => props.experimentId ?? experimentSamples.experimentId.value)\nconst resolvedDesignData = computed(() => props.designData ?? experimentSamples.designData.value ?? undefined)\n\nconst sampleGroups = useSampleGroups({\n samples: () => resolvedSamples.value,\n groups: internalGroups,\n})\nconst groupTree = sampleGroups.groupTree\n\nconst groupingEnabled = computed(() => internalGroups.value.length > 0)\nconst ungroupedSamples = sampleGroups.ungroupedSamples\nconst groupingActionMode = computed<'auto' | 'manual'>(() => props.enableSmartGroup ? 'auto' : 'manual')\n\nconst sampleSearch = useTextSearch({\n items: () => resolvedSamples.value,\n query: searchQuery,\n getText: sample => sample,\n})\nconst filteredSamples = sampleSearch.filteredItems\n\nconst {\n isAllSelected,\n toggleSelectAll,\n toggleSample,\n toggleSamplesSelection,\n isFullySelected,\n isPartiallySelected,\n} = useSampleSelectorSelection({\n selected: () => props.modelValue,\n samples: () => resolvedSamples.value,\n emitSelected: samples => emit('update:modelValue', samples),\n})\n\n// Expand/collapse\nfunction toggleGroupExpanded(groupName: string) {\n groupExpansion.toggle(groupName)\n}\n\nfunction isGroupExpanded(groupName: string): boolean {\n return groupExpansion.isExpanded(groupName)\n}\n\nfunction expandAllGroups() {\n groupExpansion.setExpanded([...collectNodeKeys(groupTree.value), '__ungrouped__'])\n}\n\nfunction collapseAllGroups() {\n groupExpansion.collapseAll()\n}\n\n// Smart group\nfunction handleSmartGroupApply(result: AutoGroupResult) {\n emit('smartGroup', result)\n emit('update:groups', result.groups)\n}\n\nfunction openGroupingModal(mode: 'auto' | 'manual') {\n groupingModalMode.value = mode\n showSmartGroupModal.value = true\n}\n\n// Group management\nfunction clearGroups() {\n internalGroups.value = []\n}\n\nfunction deleteGroups(groupNames: string[]) {\n internalGroups.value = removeSampleGroups(internalGroups.value, groupNames)\n}\n\nfunction removeSampleFromGroup(sample: string, groupName: string) {\n internalGroups.value = removeSampleFromSampleGroup(internalGroups.value, sample, groupName)\n}\n\n// Color picker\nfunction openColorPicker(groupNames: string[], event: Event) {\n event.stopPropagation()\n editingColor.value = groupNames.length === 1\n ? { kind: 'single', name: groupNames[0] }\n : { kind: 'family', names: groupNames }\n colorPickerInput.value?.click()\n}\n\nfunction handleColorChange(event: Event) {\n const edit = editingColor.value\n if (!edit) return\n\n const newColor = (event.target as HTMLInputElement).value\n internalGroups.value = applySampleGroupColorEdit(internalGroups.value, edit, newColor)\n editingColor.value = null\n}\n\nfunction getGroupColor(groupName: string): string {\n return sampleGroups.getGroupColor(groupName)\n}\n\nconst colorPickerSeed = computed(() => {\n return getSampleGroupColorEditSeed(editingColor.value, getGroupColor)\n})\n\n// Bundle of handlers handed to every level of the recursive group tree.\nconst treeContext = computed<SampleGroupTreeContext>(() => ({\n isExpanded: isGroupExpanded,\n toggleExpanded: toggleGroupExpanded,\n isSampleSelected: sample => props.modelValue.includes(sample),\n isFullySelected,\n isPartiallySelected,\n toggleSamples: toggleSamplesSelection,\n toggleSample,\n removeGroups: deleteGroups,\n removeSampleFromGroup,\n openColorPicker,\n draggingSample: () => draggingSample.value,\n dragOverGroup: () => dragOverGroup.value,\n draggingGroup: () => draggingGroup.value,\n draggingGroupKind: () => draggingGroupKind.value,\n reorderTarget: () => reorderTarget.value,\n reorderPosition: () => reorderPosition.value,\n onSampleDragStart: handleDragStart,\n onSampleDragEnd: handleDragEnd,\n onSampleDragOver: handleDragOver,\n onSampleDragLeave: handleDragLeave,\n onSampleDrop: handleDrop,\n onGroupDragStart: handleGroupDragStart,\n onGroupDragEnd: handleGroupDragEnd,\n onGroupDragOver: handleGroupDragOver,\n onGroupDragLeave: handleGroupDragLeave,\n onGroupDrop: handleGroupDrop,\n onGroupReorderKeydown: handleGroupReorderKeydown,\n}))\n\n// New group\nfunction addNewGroup() {\n const newGroup = createSampleGroup(newGroupName.value, internalGroups.value)\n if (!newGroup) return\n internalGroups.value = [...internalGroups.value, newGroup]\n newGroupName.value = ''\n}\n\nconst groupingLabel = computed(() => (props.enableSmartGroup ? 'Smart group' : 'Group'))\n\n/** Selected samples among the resolved list, for the select-all header. */\nconst selectedCount = computed(() => resolvedSamples.value.filter(s => props.modelValue.includes(s)).length)\n\n/** True while a loading, error or empty placeholder stands in for the body. */\nconst isPlaceholderState = computed(() =>\n props.loading || Boolean(props.error) || resolvedSamples.value.length === 0,\n)\n\ndefineExpose({ handleSmartGroupApply })\n</script>\n\n<template>\n <div class=\"mint-sample-selector\">\n <div class=\"mint-sample-selector__header\">\n <span class=\"mint-sample-selector__title\">Samples</span>\n <!-- A narrow selector shows \"N sel.\"; screen readers keep \"N selected\". -->\n <span class=\"mint-sample-selector__selected\">\n <span class=\"mint-sample-selector__selected-full\">{{ selectedCount }} selected</span>\n <span class=\"mint-sample-selector__selected-short\" aria-hidden=\"true\">{{ selectedCount }} sel.</span>\n </span>\n <div v-if=\"!isPlaceholderState && enableGrouping\" class=\"mint-sample-selector__header-actions\">\n <BaseButton\n variant=\"ghost\"\n size=\"sm\"\n :icon=\"enableSmartGroup ? WandSparkles : Layers\"\n :title=\"groupingLabel\"\n class=\"mint-sample-selector__action-btn mint-sample-selector__group-btn\"\n @click=\"openGroupingModal(groupingActionMode)\"\n >\n {{ groupingLabel }}\n </BaseButton>\n <BaseButton\n v-if=\"internalGroups.length > 0\"\n variant=\"ghost\"\n size=\"sm\"\n :icon=\"RotateCcw\"\n label=\"Clear all groups\"\n class=\"mint-sample-selector__action-btn\"\n @click=\"clearGroups\"\n />\n </div>\n </div>\n\n <div v-if=\"error\" class=\"mint-sample-selector__state\">\n <slot name=\"error\" :error=\"error\">\n <DataStateInternal state=\"error\" size=\"sm\" :title=\"error\" />\n </slot>\n </div>\n\n <div v-else-if=\"loading\" class=\"mint-sample-selector__state\">\n <slot name=\"loading\">\n <DataStateInternal state=\"loading\" size=\"sm\" loading-label=\"Loading samples\" />\n </slot>\n </div>\n\n <div v-else-if=\"resolvedSamples.length === 0\" class=\"mint-sample-selector__state\">\n <slot name=\"empty\">\n <DataStateInternal state=\"empty\" size=\"sm\" :title=\"emptyText\" />\n </slot>\n </div>\n\n <!-- Select-all header row -->\n <div\n v-if=\"!isPlaceholderState\"\n :class=\"['mint-sample-selector__select-all', { 'mint-sample-selector__select-all--grouped': groupingEnabled }]\"\n @click=\"toggleSelectAll\"\n >\n <BaseCheckbox\n :model-value=\"isAllSelected\"\n :indeterminate=\"selectedCount > 0 && !isAllSelected\"\n size=\"sm\"\n aria-label=\"Select all samples\"\n class=\"mint-sample-selector__checkbox\"\n @click.stop\n @update:model-value=\"toggleSelectAll\"\n />\n <span class=\"mint-sample-selector__select-all-label\">All samples</span>\n <span class=\"mint-sample-selector__select-all-count\">{{ selectedCount }}/{{ resolvedSamples.length }}</span>\n </div>\n\n <!-- Grouped View -->\n <div v-if=\"!isPlaceholderState && groupingEnabled\" class=\"mint-sample-selector__grouped\">\n <!-- Groups Header -->\n <div class=\"mint-sample-selector__groups-header\">\n <span class=\"mint-sample-selector__groups-title\">Groups ({{ internalGroups.length }})</span>\n <div class=\"mint-sample-selector__groups-controls\">\n <button type=\"button\" class=\"mint-sample-selector__expand-btn\" @click=\"expandAllGroups\" title=\"Expand all\" aria-label=\"Expand all groups\">\n <svg class=\"mint-sample-selector__expand-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n </button>\n <button type=\"button\" class=\"mint-sample-selector__expand-btn\" @click=\"collapseAllGroups\" title=\"Collapse all\" aria-label=\"Collapse all groups\">\n <svg class=\"mint-sample-selector__expand-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </div>\n </div>\n\n <!-- Hierarchical Tree -->\n <div class=\"mint-sample-selector__tree\">\n <SampleSelectorGroupNode\n v-for=\"node in groupTree\"\n :key=\"node.key\"\n :node=\"node\"\n :depth=\"0\"\n :ctx=\"treeContext\"\n />\n\n <!-- Empty state -->\n <div v-if=\"internalGroups.length === 0\" class=\"mint-sample-selector__hint\">\n Click Smart to auto-group samples\n </div>\n </div>\n\n <!-- Ungrouped Samples Section -->\n <div v-if=\"ungroupedSamples.length > 0\" class=\"mint-sample-selector__ungrouped\">\n <div\n class=\"mint-sample-selector__ungrouped-header\"\n @click=\"toggleGroupExpanded('__ungrouped__')\"\n >\n <svg\n :class=\"[\n 'mint-sample-selector__chevron',\n isGroupExpanded('__ungrouped__') ? 'mint-sample-selector__chevron--open' : '',\n ]\"\n fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n <span class=\"mint-sample-selector__ungrouped-label\">Ungrouped</span>\n <span class=\"mint-sample-selector__ungrouped-count\">{{ ungroupedSamples.length }}</span>\n </div>\n\n <Transition name=\"mint-collapse\">\n <div\n v-if=\"isGroupExpanded('__ungrouped__')\"\n class=\"mint-sample-selector__ungrouped-list\"\n >\n <SampleSelectorSampleRow\n v-for=\"sample in ungroupedSamples\"\n :key=\"sample\"\n :sample=\"sample\"\n :selected=\"modelValue.includes(sample)\"\n :dragging=\"draggingSample === sample\"\n @dragstart=\"handleDragStart(sample, null, $event)\"\n @dragend=\"handleDragEnd\"\n @toggle=\"toggleSample(sample)\"\n />\n </div>\n </Transition>\n </div>\n\n <!-- New Group Input -->\n <div class=\"mint-sample-selector__new-group\">\n <BaseInput\n v-model=\"newGroupName\"\n placeholder=\"New group name...\"\n class=\"mint-sample-selector__new-group-input\"\n @keyup.enter=\"addNewGroup\"\n />\n <BaseButton\n variant=\"ghost\"\n size=\"sm\"\n :disabled=\"!newGroupName.trim()\"\n class=\"mint-sample-selector__new-group-btn\"\n @click=\"addNewGroup\"\n >\n Add\n </BaseButton>\n </div>\n\n <!-- Hidden color picker input -->\n <input\n ref=\"colorPickerInput\"\n type=\"color\"\n class=\"mint-sample-selector__color-input\"\n :value=\"colorPickerSeed\"\n @change=\"handleColorChange\"\n />\n </div>\n\n <!-- Flat View (when no groups) -->\n <div v-if=\"!isPlaceholderState && !groupingEnabled\" class=\"mint-sample-selector__flat\">\n <!-- Search -->\n <div class=\"mint-sample-selector__search\">\n <svg class=\"mint-sample-selector__search-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\" />\n </svg>\n <input\n v-model=\"searchQuery\"\n type=\"text\"\n placeholder=\"Search samples...\"\n class=\"mint-sample-selector__search-input\"\n />\n </div>\n\n <!-- Flat samples list -->\n <div class=\"mint-sample-selector__flat-list\">\n <div\n v-for=\"sample in filteredSamples\"\n :key=\"sample\"\n class=\"mint-sample-selector__flat-item\"\n :title=\"sample\"\n :aria-label=\"`Sample: ${sample}`\"\n >\n <BaseCheckbox\n :model-value=\"modelValue.includes(sample)\"\n size=\"sm\"\n :aria-label=\"`Select ${sample}`\"\n class=\"mint-sample-selector__checkbox\"\n @update:model-value=\"toggleSample(sample)\"\n />\n <span class=\"mint-sample-selector__flat-name\">{{ sample }}</span>\n </div>\n\n <div v-if=\"filteredSamples.length === 0 && searchQuery.trim()\" class=\"mint-sample-selector__hint\">\n No samples match \"{{ searchQuery }}\"\n </div>\n </div>\n </div>\n\n <!-- Smart grouping modal -->\n <AutoGroupModalInternal\n v-model:open=\"showSmartGroupModal\"\n :samples=\"resolvedSamples\"\n :groups=\"internalGroups\"\n :initial-mode=\"groupingModalMode\"\n :experiment-id=\"resolvedExperimentId\"\n :design-data=\"resolvedDesignData\"\n @apply=\"handleSmartGroupApply\"\n />\n\n <!-- Group reorder announcements for screen readers -->\n <div role=\"status\" aria-live=\"polite\" :style=\"announcerStyle\">{{ reorderAnnouncement }}</div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Interactive sample list with drag-and-drop group assignment, color labeling, and smart-group support. */\nimport { ref, computed } from 'vue'\nimport { Layers, RotateCcw, WandSparkles } from 'lucide-vue-next'\nimport BaseButton from './BaseButton.vue'\nimport BaseCheckbox from './BaseCheckbox.vue'\nimport BaseInput from './BaseInput.vue'\nimport AutoGroupModalInternal from './internal/AutoGroupModalInternal.vue'\nimport SampleSelectorSampleRow from './SampleSelectorSampleRow.vue'\nimport SampleSelectorGroupNode from './SampleSelectorGroupNode.vue'\nimport { collectNodeKeys, type SampleGroupTreeContext } from './SampleSelector.tree'\nimport type { SampleGroup } from '../types'\nimport type { AutoGroupResult } from '../types/auto-group'\nimport { useTextSearch } from '../composables/useTextSearch'\nimport { useSampleGroups } from '../composables/useSampleGroups'\nimport { useExpansionSet } from '../composables/useExpansionSet'\nimport { useExperimentSamples } from '../composables/useExperimentSamples'\nimport { useSampleSelectorDrag } from './SampleSelector.drag'\nimport { useSampleSelectorSelection } from './SampleSelector.selection'\nimport {\n applySampleGroupColorEdit,\n createSampleGroup,\n getSampleGroupColorEditSeed,\n type ColorEdit,\n} from './SampleSelector.colors'\nimport {\n removeSampleFromGroup as removeSampleFromSampleGroup,\n removeSampleGroups,\n} from './SampleSelector.groups'\nimport DataStateInternal from './internal/DataStateInternal.vue'\n\ninterface Props {\n samples?: string[]\n modelValue: string[]\n groups?: SampleGroup[]\n enableGrouping?: boolean\n enableSmartGroup?: boolean\n experimentId?: number\n designData?: Record<string, unknown>\n autoloadExperimentData?: boolean\n /** Replace the selector body with a loading placeholder. */\n loading?: boolean\n /**\n * Error message. When set, the selector body is replaced by an error state.\n * `null`/`undefined` renders normally.\n */\n error?: string | null\n /** Empty-state headline shown when there are no samples to select from. */\n emptyText?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n samples: () => [],\n groups: () => [],\n enableGrouping: true,\n enableSmartGroup: true,\n experimentId: undefined,\n designData: undefined,\n autoloadExperimentData: true,\n loading: false,\n error: null,\n emptyText: 'No samples available',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [samples: string[]]\n 'update:groups': [groups: SampleGroup[]]\n smartGroup: [result: AutoGroupResult]\n}>()\n\n// UI State\nconst showSmartGroupModal = ref(false)\nconst groupingModalMode = ref<'auto' | 'manual'>('auto')\nconst newGroupName = ref('')\nconst editingColor = ref<ColorEdit | null>(null)\nconst colorPickerInput = ref<HTMLInputElement | null>(null)\nconst searchQuery = ref('')\nconst groupExpansion = useExpansionSet()\n\n// Computed: groups from props\nconst internalGroups = computed({\n get: () => props.groups,\n set: (value) => emit('update:groups', value),\n})\n\nconst {\n announcement: reorderAnnouncement,\n announcerStyle,\n draggingSample,\n dragOverGroup,\n draggingGroup,\n draggingGroupKind,\n reorderTarget,\n reorderPosition,\n handleDragStart,\n handleDragEnd,\n handleDragOver,\n handleDragLeave,\n handleDrop,\n handleGroupDragStart,\n handleGroupReorderKeydown,\n handleGroupDragEnd,\n handleGroupDragOver,\n handleGroupDragLeave,\n handleGroupDrop,\n} = useSampleSelectorDrag(internalGroups)\n\nconst providedSamples = computed(() => props.samples)\nconst shouldLoadExperimentSamples = computed(() =>\n props.autoloadExperimentData && providedSamples.value.length === 0,\n)\nconst experimentSamples = useExperimentSamples({\n experimentId: () => props.experimentId,\n designData: () => props.designData,\n enabled: shouldLoadExperimentSamples,\n})\nconst resolvedSamples = computed(() =>\n providedSamples.value.length > 0 ? providedSamples.value : experimentSamples.samples.value,\n)\nconst resolvedExperimentId = computed(() => props.experimentId ?? experimentSamples.experimentId.value)\nconst resolvedDesignData = computed(() => props.designData ?? experimentSamples.designData.value ?? undefined)\n\nconst sampleGroups = useSampleGroups({\n samples: () => resolvedSamples.value,\n groups: internalGroups,\n})\nconst groupTree = sampleGroups.groupTree\n\nconst groupingEnabled = computed(() => internalGroups.value.length > 0)\nconst ungroupedSamples = sampleGroups.ungroupedSamples\nconst groupingActionMode = computed<'auto' | 'manual'>(() => props.enableSmartGroup ? 'auto' : 'manual')\n\nconst sampleSearch = useTextSearch({\n items: () => resolvedSamples.value,\n query: searchQuery,\n getText: sample => sample,\n})\nconst filteredSamples = sampleSearch.filteredItems\n\nconst {\n isAllSelected,\n toggleSelectAll,\n toggleSample,\n toggleSamplesSelection,\n isFullySelected,\n isPartiallySelected,\n} = useSampleSelectorSelection({\n selected: () => props.modelValue,\n samples: () => resolvedSamples.value,\n emitSelected: samples => emit('update:modelValue', samples),\n})\n\n// Expand/collapse\nfunction toggleGroupExpanded(groupName: string) {\n groupExpansion.toggle(groupName)\n}\n\nfunction isGroupExpanded(groupName: string): boolean {\n return groupExpansion.isExpanded(groupName)\n}\n\nfunction expandAllGroups() {\n groupExpansion.setExpanded([...collectNodeKeys(groupTree.value), '__ungrouped__'])\n}\n\nfunction collapseAllGroups() {\n groupExpansion.collapseAll()\n}\n\n// Smart group\nfunction handleSmartGroupApply(result: AutoGroupResult) {\n emit('smartGroup', result)\n emit('update:groups', result.groups)\n}\n\nfunction openGroupingModal(mode: 'auto' | 'manual') {\n groupingModalMode.value = mode\n showSmartGroupModal.value = true\n}\n\n// Group management\nfunction clearGroups() {\n internalGroups.value = []\n}\n\nfunction deleteGroups(groupNames: string[]) {\n internalGroups.value = removeSampleGroups(internalGroups.value, groupNames)\n}\n\nfunction removeSampleFromGroup(sample: string, groupName: string) {\n internalGroups.value = removeSampleFromSampleGroup(internalGroups.value, sample, groupName)\n}\n\n// Color picker\nfunction openColorPicker(groupNames: string[], event: Event) {\n event.stopPropagation()\n editingColor.value = groupNames.length === 1\n ? { kind: 'single', name: groupNames[0] }\n : { kind: 'family', names: groupNames }\n colorPickerInput.value?.click()\n}\n\nfunction handleColorChange(event: Event) {\n const edit = editingColor.value\n if (!edit) return\n\n const newColor = (event.target as HTMLInputElement).value\n internalGroups.value = applySampleGroupColorEdit(internalGroups.value, edit, newColor)\n editingColor.value = null\n}\n\nfunction getGroupColor(groupName: string): string {\n return sampleGroups.getGroupColor(groupName)\n}\n\nconst colorPickerSeed = computed(() => {\n return getSampleGroupColorEditSeed(editingColor.value, getGroupColor)\n})\n\n// Bundle of handlers handed to every level of the recursive group tree.\nconst treeContext = computed<SampleGroupTreeContext>(() => ({\n isExpanded: isGroupExpanded,\n toggleExpanded: toggleGroupExpanded,\n isSampleSelected: sample => props.modelValue.includes(sample),\n isFullySelected,\n isPartiallySelected,\n toggleSamples: toggleSamplesSelection,\n toggleSample,\n removeGroups: deleteGroups,\n removeSampleFromGroup,\n openColorPicker,\n draggingSample: () => draggingSample.value,\n dragOverGroup: () => dragOverGroup.value,\n draggingGroup: () => draggingGroup.value,\n draggingGroupKind: () => draggingGroupKind.value,\n reorderTarget: () => reorderTarget.value,\n reorderPosition: () => reorderPosition.value,\n onSampleDragStart: handleDragStart,\n onSampleDragEnd: handleDragEnd,\n onSampleDragOver: handleDragOver,\n onSampleDragLeave: handleDragLeave,\n onSampleDrop: handleDrop,\n onGroupDragStart: handleGroupDragStart,\n onGroupDragEnd: handleGroupDragEnd,\n onGroupDragOver: handleGroupDragOver,\n onGroupDragLeave: handleGroupDragLeave,\n onGroupDrop: handleGroupDrop,\n onGroupReorderKeydown: handleGroupReorderKeydown,\n}))\n\n// New group\nfunction addNewGroup() {\n const newGroup = createSampleGroup(newGroupName.value, internalGroups.value)\n if (!newGroup) return\n internalGroups.value = [...internalGroups.value, newGroup]\n newGroupName.value = ''\n}\n\nconst groupingLabel = computed(() => (props.enableSmartGroup ? 'Smart group' : 'Group'))\n\n/** Selected samples among the resolved list, for the select-all header. */\nconst selectedCount = computed(() => resolvedSamples.value.filter(s => props.modelValue.includes(s)).length)\n\n/** True while a loading, error or empty placeholder stands in for the body. */\nconst isPlaceholderState = computed(() =>\n props.loading || Boolean(props.error) || resolvedSamples.value.length === 0,\n)\n\ndefineExpose({ handleSmartGroupApply })\n</script>\n\n<template>\n <div class=\"mint-sample-selector\">\n <div class=\"mint-sample-selector__header\">\n <span class=\"mint-sample-selector__title\">Samples</span>\n <!-- A narrow selector shows \"N sel.\"; screen readers keep \"N selected\". -->\n <span class=\"mint-sample-selector__selected\">\n <span class=\"mint-sample-selector__selected-full\">{{ selectedCount }} selected</span>\n <span class=\"mint-sample-selector__selected-short\" aria-hidden=\"true\">{{ selectedCount }} sel.</span>\n </span>\n <div v-if=\"!isPlaceholderState && enableGrouping\" class=\"mint-sample-selector__header-actions\">\n <BaseButton\n variant=\"ghost\"\n size=\"sm\"\n :icon=\"enableSmartGroup ? WandSparkles : Layers\"\n :title=\"groupingLabel\"\n class=\"mint-sample-selector__action-btn mint-sample-selector__group-btn\"\n @click=\"openGroupingModal(groupingActionMode)\"\n >\n {{ groupingLabel }}\n </BaseButton>\n <BaseButton\n v-if=\"internalGroups.length > 0\"\n variant=\"ghost\"\n size=\"sm\"\n :icon=\"RotateCcw\"\n label=\"Clear all groups\"\n class=\"mint-sample-selector__action-btn\"\n @click=\"clearGroups\"\n />\n </div>\n </div>\n\n <div v-if=\"error\" class=\"mint-sample-selector__state\">\n <slot name=\"error\" :error=\"error\">\n <DataStateInternal state=\"error\" size=\"sm\" :title=\"error\" />\n </slot>\n </div>\n\n <div v-else-if=\"loading\" class=\"mint-sample-selector__state\">\n <slot name=\"loading\">\n <DataStateInternal state=\"loading\" size=\"sm\" loading-label=\"Loading samples\" />\n </slot>\n </div>\n\n <div v-else-if=\"resolvedSamples.length === 0\" class=\"mint-sample-selector__state\">\n <slot name=\"empty\">\n <DataStateInternal state=\"empty\" size=\"sm\" :title=\"emptyText\" />\n </slot>\n </div>\n\n <!-- Select-all header row -->\n <div\n v-if=\"!isPlaceholderState\"\n :class=\"['mint-sample-selector__select-all', { 'mint-sample-selector__select-all--grouped': groupingEnabled }]\"\n @click=\"toggleSelectAll\"\n >\n <BaseCheckbox\n :model-value=\"isAllSelected\"\n :indeterminate=\"selectedCount > 0 && !isAllSelected\"\n size=\"sm\"\n aria-label=\"Select all samples\"\n class=\"mint-sample-selector__checkbox\"\n @click.stop\n @update:model-value=\"toggleSelectAll\"\n />\n <span class=\"mint-sample-selector__select-all-label\">All samples</span>\n <span class=\"mint-sample-selector__select-all-count\">{{ selectedCount }}/{{ resolvedSamples.length }}</span>\n </div>\n\n <!-- Grouped View -->\n <div v-if=\"!isPlaceholderState && groupingEnabled\" class=\"mint-sample-selector__grouped\">\n <!-- Groups Header -->\n <div class=\"mint-sample-selector__groups-header\">\n <span class=\"mint-sample-selector__groups-title\">Groups ({{ internalGroups.length }})</span>\n <div class=\"mint-sample-selector__groups-controls\">\n <button type=\"button\" class=\"mint-sample-selector__expand-btn\" @click=\"expandAllGroups\" title=\"Expand all\" aria-label=\"Expand all groups\">\n <svg class=\"mint-sample-selector__expand-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n </button>\n <button type=\"button\" class=\"mint-sample-selector__expand-btn\" @click=\"collapseAllGroups\" title=\"Collapse all\" aria-label=\"Collapse all groups\">\n <svg class=\"mint-sample-selector__expand-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </div>\n </div>\n\n <!-- Hierarchical Tree -->\n <div class=\"mint-sample-selector__tree\">\n <SampleSelectorGroupNode\n v-for=\"node in groupTree\"\n :key=\"node.key\"\n :node=\"node\"\n :depth=\"0\"\n :ctx=\"treeContext\"\n />\n\n <!-- Empty state -->\n <div v-if=\"internalGroups.length === 0\" class=\"mint-sample-selector__hint\">\n Click Smart to auto-group samples\n </div>\n </div>\n\n <!-- Ungrouped Samples Section -->\n <div v-if=\"ungroupedSamples.length > 0\" class=\"mint-sample-selector__ungrouped\">\n <div\n class=\"mint-sample-selector__ungrouped-header\"\n @click=\"toggleGroupExpanded('__ungrouped__')\"\n >\n <svg\n :class=\"[\n 'mint-sample-selector__chevron',\n isGroupExpanded('__ungrouped__') ? 'mint-sample-selector__chevron--open' : '',\n ]\"\n fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n <span class=\"mint-sample-selector__ungrouped-label\">Ungrouped</span>\n <span class=\"mint-sample-selector__ungrouped-count\">{{ ungroupedSamples.length }}</span>\n </div>\n\n <Transition name=\"mint-collapse\">\n <div\n v-if=\"isGroupExpanded('__ungrouped__')\"\n class=\"mint-sample-selector__ungrouped-list\"\n >\n <SampleSelectorSampleRow\n v-for=\"sample in ungroupedSamples\"\n :key=\"sample\"\n :sample=\"sample\"\n :selected=\"modelValue.includes(sample)\"\n :dragging=\"draggingSample === sample\"\n @dragstart=\"handleDragStart(sample, null, $event)\"\n @dragend=\"handleDragEnd\"\n @toggle=\"toggleSample(sample)\"\n />\n </div>\n </Transition>\n </div>\n\n <!-- New Group Input -->\n <div class=\"mint-sample-selector__new-group\">\n <BaseInput\n v-model=\"newGroupName\"\n placeholder=\"New group name...\"\n class=\"mint-sample-selector__new-group-input\"\n @keyup.enter=\"addNewGroup\"\n />\n <BaseButton\n variant=\"ghost\"\n size=\"sm\"\n :disabled=\"!newGroupName.trim()\"\n class=\"mint-sample-selector__new-group-btn\"\n @click=\"addNewGroup\"\n >\n Add\n </BaseButton>\n </div>\n\n <!-- Hidden color picker input -->\n <input\n ref=\"colorPickerInput\"\n type=\"color\"\n class=\"mint-sample-selector__color-input\"\n :value=\"colorPickerSeed\"\n @change=\"handleColorChange\"\n />\n </div>\n\n <!-- Flat View (when no groups) -->\n <div v-if=\"!isPlaceholderState && !groupingEnabled\" class=\"mint-sample-selector__flat\">\n <!-- Search -->\n <div class=\"mint-sample-selector__search\">\n <svg class=\"mint-sample-selector__search-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z\" />\n </svg>\n <input\n v-model=\"searchQuery\"\n type=\"text\"\n placeholder=\"Search samples...\"\n class=\"mint-sample-selector__search-input\"\n />\n </div>\n\n <!-- Flat samples list -->\n <div class=\"mint-sample-selector__flat-list\">\n <div\n v-for=\"sample in filteredSamples\"\n :key=\"sample\"\n class=\"mint-sample-selector__flat-item\"\n :title=\"sample\"\n :aria-label=\"`Sample: ${sample}`\"\n >\n <BaseCheckbox\n :model-value=\"modelValue.includes(sample)\"\n size=\"sm\"\n :aria-label=\"`Select ${sample}`\"\n class=\"mint-sample-selector__checkbox\"\n @update:model-value=\"toggleSample(sample)\"\n />\n <span class=\"mint-sample-selector__flat-name\">{{ sample }}</span>\n </div>\n\n <div v-if=\"filteredSamples.length === 0 && searchQuery.trim()\" class=\"mint-sample-selector__hint\">\n No samples match \"{{ searchQuery }}\"\n </div>\n </div>\n </div>\n\n <!-- Smart grouping modal -->\n <AutoGroupModalInternal\n v-model:open=\"showSmartGroupModal\"\n :samples=\"resolvedSamples\"\n :groups=\"internalGroups\"\n :initial-mode=\"groupingModalMode\"\n :experiment-id=\"resolvedExperimentId\"\n :design-data=\"resolvedDesignData\"\n @apply=\"handleSmartGroupApply\"\n />\n\n <!-- Group reorder announcements for screen readers -->\n <div role=\"status\" aria-live=\"polite\" :style=\"announcerStyle\">{{ reorderAnnouncement }}</div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Collapsible tree visualizing the biological sample hierarchy (study → experiment → plate → sample → cell line → passage → clone → treatment). */\nimport { h, Transition, useId, type VNode } from 'vue'\nimport type { TreeNodeType, BadgeVariant, TreeNode } from '../types'\nimport { useExpansionSet } from '../composables/useExpansionSet'\nimport DataStateInternal from './internal/DataStateInternal.vue'\n\n/** Folder glyph kept from the pre-1.2 hand-rolled empty state. */\nconst EMPTY_ICON_PATH\n = 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z'\n\ninterface Props {\n nodes: TreeNode[]\n defaultExpandedIds?: string[]\n expandAll?: boolean\n showIcons?: boolean\n showCounts?: boolean\n maxDepth?: number\n size?: 'sm' | 'md' | 'lg'\n /** Replace the tree with a loading placeholder. */\n loading?: boolean\n /**\n * Error message. When set, the tree is replaced by an error state.\n * `null`/`undefined` renders normally.\n */\n error?: string | null\n /**\n * Selected node id; bind it with v-model on selected-id. Binding it, even to\n * `null`, turns on selection. The matching row is highlighted, rows take\n * keyboard focus and Enter or Space selects. Left unbound, rows are not selectable.\n */\n selectedId?: string | null\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n defaultExpandedIds: () => [],\n expandAll: false,\n showIcons: true,\n showCounts: true,\n size: 'md',\n loading: false,\n error: null,\n})\n\nconst emit = defineEmits<{\n 'node-click': [node: TreeNode]\n 'expand': [nodeId: string]\n 'collapse': [nodeId: string]\n 'update:selectedId': [nodeId: string]\n}>()\n\n// Default icons by type (Lucide SVG paths - arrays to support multi-path icons)\ninterface IconElement {\n tag: 'path' | 'circle' | 'rect'\n attrs: Record<string, string | number>\n}\n\nconst typeIcons: Record<TreeNodeType, IconElement[]> = {\n study: [\n { tag: 'path', attrs: { d: 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z' } },\n ],\n experiment: [\n { tag: 'path', attrs: { d: 'M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2' } },\n { tag: 'path', attrs: { d: 'M6.453 15h11.094' } },\n { tag: 'path', attrs: { d: 'M8.5 2h7' } },\n ],\n plate: [\n { tag: 'rect', attrs: { width: '7', height: '7', x: '3', y: '3', rx: '1' } },\n { tag: 'rect', attrs: { width: '7', height: '7', x: '14', y: '3', rx: '1' } },\n { tag: 'rect', attrs: { width: '7', height: '7', x: '14', y: '14', rx: '1' } },\n { tag: 'rect', attrs: { width: '7', height: '7', x: '3', y: '14', rx: '1' } },\n ],\n sample: [\n { tag: 'path', attrs: { d: 'M14.5 2v17.5c0 1.4-1.1 2.5-2.5 2.5c-1.4 0-2.5-1.1-2.5-2.5V2' } },\n { tag: 'path', attrs: { d: 'M8.5 2h7' } },\n { tag: 'path', attrs: { d: 'M14.5 16h-5' } },\n ],\n cell_line: [\n { tag: 'circle', attrs: { cx: '12', cy: '12', r: '10' } },\n { tag: 'circle', attrs: { cx: '12', cy: '12', r: '3' } },\n ],\n passage: [\n { tag: 'path', attrs: { d: 'M3 5h.01' } },\n { tag: 'path', attrs: { d: 'M3 12h.01' } },\n { tag: 'path', attrs: { d: 'M3 19h.01' } },\n { tag: 'path', attrs: { d: 'M8 5h13' } },\n { tag: 'path', attrs: { d: 'M8 12h13' } },\n { tag: 'path', attrs: { d: 'M8 19h13' } },\n ],\n clone: [\n { tag: 'rect', attrs: { width: '14', height: '14', x: '8', y: '8', rx: '2', ry: '2' } },\n { tag: 'path', attrs: { d: 'M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2' } },\n ],\n treatment: [\n { tag: 'path', attrs: { d: 'm18 2 4 4' } },\n { tag: 'path', attrs: { d: 'm17 7 3-3' } },\n { tag: 'path', attrs: { d: 'M19 9 8.7 19.3c-1 1-2.5 1-3.4 0l-.6-.6c-1-1-1-2.5 0-3.4L15 5' } },\n { tag: 'path', attrs: { d: 'm9 11 4 4' } },\n { tag: 'path', attrs: { d: 'm5 19-3 3' } },\n { tag: 'path', attrs: { d: 'm14 4 6 6' } },\n ],\n folder: [\n { tag: 'path', attrs: { d: 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z' } },\n ],\n custom: [\n { tag: 'path', attrs: { d: 'M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915' } },\n { tag: 'circle', attrs: { cx: '12', cy: '12', r: '3' } },\n ],\n}\n\n// Collect all node IDs for expand all\nfunction collectAllIds(nodes: TreeNode[]): string[] {\n const ids: string[] = []\n function traverse(node: TreeNode) {\n ids.push(node.id)\n if (node.children) {\n node.children.forEach(traverse)\n }\n }\n nodes.forEach(traverse)\n return ids\n}\n\nconst expansion = useExpansionSet({\n defaultIds: () => props.defaultExpandedIds,\n allIds: () => collectAllIds(props.nodes),\n expandAll: () => props.expandAll,\n})\n\nfunction isExpanded(nodeId: string): boolean {\n return expansion.isExpanded(nodeId)\n}\n\nfunction toggleExpand(node: TreeNode) {\n const expanded = expansion.toggle(node.id)\n if (expanded) {\n emit('expand', node.id)\n } else {\n emit('collapse', node.id)\n }\n}\n\nfunction handleNodeClick(node: TreeNode) {\n emit('node-click', node)\n emit('update:selectedId', node.id)\n}\n\nfunction handleHeaderKeydown(event: KeyboardEvent, node: TreeNode) {\n // Keys pressed on the nested expand toggle belong to the toggle.\n if (props.selectedId === undefined || event.target !== event.currentTarget) return\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n handleNodeClick(node)\n}\n\nfunction getIconElements(node: TreeNode): IconElement[] {\n if (node.icon) return [{ tag: 'path', attrs: { d: node.icon } }]\n return typeIcons[node.type || 'custom']\n}\n\nfunction getBadgeContent(node: TreeNode): string | number | undefined {\n if (node.badge !== undefined) return node.badge\n if (props.showCounts && node.children && node.children.length > 0) {\n return node.children.length\n }\n return undefined\n}\n\nfunction getBadgeVariant(node: TreeNode): BadgeVariant {\n return node.badgeVariant || 'default'\n}\n\nfunction hasChildren(node: TreeNode): boolean {\n return !!node.children && node.children.length > 0\n}\n\nfunction canShowChildren(node: TreeNode, depth: number): boolean {\n if (!hasChildren(node)) return false\n if (props.maxDepth !== undefined && depth >= props.maxDepth) return false\n return isExpanded(node.id)\n}\n\nconst treeId = useId()\n\n// Render tree node recursively\nfunction renderNode(node: TreeNode, depth: number): VNode {\n const children = node.children\n const expanded = isExpanded(node.id)\n const canExpand = hasChildren(node)\n const showChildNodes = canShowChildren(node, depth)\n const badge = getBadgeContent(node)\n const badgeVar = getBadgeVariant(node)\n const iconElements = getIconElements(node)\n const selectable = props.selectedId !== undefined\n const selected = selectable && props.selectedId === node.id\n // The child group sits beside the header, so the header treeitem claims it with aria-owns.\n const groupId = `${treeId}-group-${encodeURIComponent(node.id)}`\n\n const header = h(\n 'div',\n {\n class: ['mint-sample-tree__node-header', selected ? 'mint-sample-tree__node-header--selected' : ''],\n // The focusable header is the treeitem, so its selected state is announced on focus.\n role: 'treeitem',\n 'aria-expanded': canExpand ? expanded : undefined,\n 'aria-selected': selectable ? selected : undefined,\n 'aria-owns': showChildNodes ? groupId : undefined,\n tabindex: selectable ? 0 : undefined,\n onClick: () => handleNodeClick(node),\n onKeydown: (e: KeyboardEvent) => handleHeaderKeydown(e, node),\n },\n [\n canExpand\n ? h(\n 'button',\n {\n type: 'button',\n class: 'mint-sample-tree__toggle',\n 'aria-label': expanded ? 'Collapse' : 'Expand',\n onClick: (e: Event) => {\n e.stopPropagation()\n toggleExpand(node)\n },\n },\n [\n h('svg', { fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' }, [\n h('path', { d: 'm9 18 6-6-6-6' }),\n ]),\n ]\n )\n : h('span', { class: 'mint-sample-tree__toggle-placeholder' }),\n props.showIcons\n ? h('span', { class: ['mint-sample-tree__icon', `mint-sample-tree__icon--${node.type || 'custom'}`] }, [\n h('svg', { fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' },\n iconElements.map(el => h(el.tag, el.attrs))\n ),\n ])\n : null,\n h('span', { class: ['mint-sample-tree__label', `mint-sample-tree__label--${node.type || 'custom'}`] }, node.label),\n badge !== undefined\n ? h('span', { class: ['mint-sample-tree__badge', `mint-sample-tree__badge--${badgeVar}`] }, String(badge))\n : null,\n ]\n )\n\n const childNodes =\n showChildNodes && children\n ? h(\n Transition,\n { enterActiveClass: 'mint-sample-tree__children--entering', leaveActiveClass: 'mint-sample-tree__children--leaving' },\n // The inner wrapper is the single grid row the 0fr → 1fr expand animates (4c).\n () => h('div', { id: groupId, class: 'mint-sample-tree__children', role: 'group' }, [\n h('div', { class: 'mint-sample-tree__children-inner' }, children.map((child) => renderNode(child, depth + 1))),\n ])\n )\n : null\n\n return h(\n 'div',\n {\n key: node.id,\n class: ['mint-sample-tree__node', expanded ? 'mint-sample-tree__node--expanded' : '', !canExpand ? 'mint-sample-tree__node--leaf' : ''],\n },\n [header, childNodes]\n )\n}\n\n// Render the entire tree\nfunction renderTree(): VNode[] {\n return props.nodes.map((node) => renderNode(node, 0))\n}\n</script>\n\n<template>\n <div :class=\"['mint-sample-tree', `mint-sample-tree--${size}`]\" role=\"tree\">\n <!-- Error state -->\n <div v-if=\"error\" class=\"mint-sample-tree__state\">\n <slot name=\"error\" :error=\"error\">\n <DataStateInternal state=\"error\" size=\"sm\" :title=\"error\" />\n </slot>\n </div>\n\n <!-- Loading state -->\n <div v-else-if=\"loading\" class=\"mint-sample-tree__state\">\n <slot name=\"loading\">\n <DataStateInternal state=\"loading\" size=\"sm\" loading-label=\"Loading samples\" />\n </slot>\n </div>\n\n <!-- Empty state -->\n <div v-else-if=\"nodes.length === 0\" class=\"mint-sample-tree__state\">\n <slot name=\"empty\">\n <DataStateInternal state=\"empty\" size=\"sm\" title=\"No items\" :icon-path=\"EMPTY_ICON_PATH\" />\n </slot>\n </div>\n\n <!-- Tree nodes rendered via render function -->\n <template v-else>\n <component :is=\"() => renderTree()\" />\n </template>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Collapsible tree visualizing the biological sample hierarchy (study → experiment → plate → sample → cell line → passage → clone → treatment). */\nimport { h, Transition, useId, type VNode } from 'vue'\nimport type { TreeNodeType, BadgeVariant, TreeNode } from '../types'\nimport { useExpansionSet } from '../composables/useExpansionSet'\nimport DataStateInternal from './internal/DataStateInternal.vue'\n\n/** Folder glyph kept from the pre-1.2 hand-rolled empty state. */\nconst EMPTY_ICON_PATH\n = 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z'\n\ninterface Props {\n nodes: TreeNode[]\n defaultExpandedIds?: string[]\n expandAll?: boolean\n showIcons?: boolean\n showCounts?: boolean\n maxDepth?: number\n size?: 'sm' | 'md' | 'lg'\n /** Replace the tree with a loading placeholder. */\n loading?: boolean\n /**\n * Error message. When set, the tree is replaced by an error state.\n * `null`/`undefined` renders normally.\n */\n error?: string | null\n /**\n * Selected node id; bind it with v-model on selected-id. Binding it, even to\n * `null`, turns on selection. The matching row is highlighted, rows take\n * keyboard focus and Enter or Space selects. Left unbound, rows are not selectable.\n */\n selectedId?: string | null\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n defaultExpandedIds: () => [],\n expandAll: false,\n showIcons: true,\n showCounts: true,\n size: 'md',\n loading: false,\n error: null,\n})\n\nconst emit = defineEmits<{\n 'node-click': [node: TreeNode]\n 'expand': [nodeId: string]\n 'collapse': [nodeId: string]\n 'update:selectedId': [nodeId: string]\n}>()\n\n// Default icons by type (Lucide SVG paths - arrays to support multi-path icons)\ninterface IconElement {\n tag: 'path' | 'circle' | 'rect'\n attrs: Record<string, string | number>\n}\n\nconst typeIcons: Record<TreeNodeType, IconElement[]> = {\n study: [\n { tag: 'path', attrs: { d: 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z' } },\n ],\n experiment: [\n { tag: 'path', attrs: { d: 'M14 2v6a2 2 0 0 0 .245.96l5.51 10.08A2 2 0 0 1 18 22H6a2 2 0 0 1-1.755-2.96l5.51-10.08A2 2 0 0 0 10 8V2' } },\n { tag: 'path', attrs: { d: 'M6.453 15h11.094' } },\n { tag: 'path', attrs: { d: 'M8.5 2h7' } },\n ],\n plate: [\n { tag: 'rect', attrs: { width: '7', height: '7', x: '3', y: '3', rx: '1' } },\n { tag: 'rect', attrs: { width: '7', height: '7', x: '14', y: '3', rx: '1' } },\n { tag: 'rect', attrs: { width: '7', height: '7', x: '14', y: '14', rx: '1' } },\n { tag: 'rect', attrs: { width: '7', height: '7', x: '3', y: '14', rx: '1' } },\n ],\n sample: [\n { tag: 'path', attrs: { d: 'M14.5 2v17.5c0 1.4-1.1 2.5-2.5 2.5c-1.4 0-2.5-1.1-2.5-2.5V2' } },\n { tag: 'path', attrs: { d: 'M8.5 2h7' } },\n { tag: 'path', attrs: { d: 'M14.5 16h-5' } },\n ],\n cell_line: [\n { tag: 'circle', attrs: { cx: '12', cy: '12', r: '10' } },\n { tag: 'circle', attrs: { cx: '12', cy: '12', r: '3' } },\n ],\n passage: [\n { tag: 'path', attrs: { d: 'M3 5h.01' } },\n { tag: 'path', attrs: { d: 'M3 12h.01' } },\n { tag: 'path', attrs: { d: 'M3 19h.01' } },\n { tag: 'path', attrs: { d: 'M8 5h13' } },\n { tag: 'path', attrs: { d: 'M8 12h13' } },\n { tag: 'path', attrs: { d: 'M8 19h13' } },\n ],\n clone: [\n { tag: 'rect', attrs: { width: '14', height: '14', x: '8', y: '8', rx: '2', ry: '2' } },\n { tag: 'path', attrs: { d: 'M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2' } },\n ],\n treatment: [\n { tag: 'path', attrs: { d: 'm18 2 4 4' } },\n { tag: 'path', attrs: { d: 'm17 7 3-3' } },\n { tag: 'path', attrs: { d: 'M19 9 8.7 19.3c-1 1-2.5 1-3.4 0l-.6-.6c-1-1-1-2.5 0-3.4L15 5' } },\n { tag: 'path', attrs: { d: 'm9 11 4 4' } },\n { tag: 'path', attrs: { d: 'm5 19-3 3' } },\n { tag: 'path', attrs: { d: 'm14 4 6 6' } },\n ],\n folder: [\n { tag: 'path', attrs: { d: 'M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z' } },\n ],\n custom: [\n { tag: 'path', attrs: { d: 'M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915' } },\n { tag: 'circle', attrs: { cx: '12', cy: '12', r: '3' } },\n ],\n}\n\n// Collect all node IDs for expand all\nfunction collectAllIds(nodes: TreeNode[]): string[] {\n const ids: string[] = []\n function traverse(node: TreeNode) {\n ids.push(node.id)\n if (node.children) {\n node.children.forEach(traverse)\n }\n }\n nodes.forEach(traverse)\n return ids\n}\n\nconst expansion = useExpansionSet({\n defaultIds: () => props.defaultExpandedIds,\n allIds: () => collectAllIds(props.nodes),\n expandAll: () => props.expandAll,\n})\n\nfunction isExpanded(nodeId: string): boolean {\n return expansion.isExpanded(nodeId)\n}\n\nfunction toggleExpand(node: TreeNode) {\n const expanded = expansion.toggle(node.id)\n if (expanded) {\n emit('expand', node.id)\n } else {\n emit('collapse', node.id)\n }\n}\n\nfunction handleNodeClick(node: TreeNode) {\n emit('node-click', node)\n emit('update:selectedId', node.id)\n}\n\nfunction handleHeaderKeydown(event: KeyboardEvent, node: TreeNode) {\n // Keys pressed on the nested expand toggle belong to the toggle.\n if (props.selectedId === undefined || event.target !== event.currentTarget) return\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n handleNodeClick(node)\n}\n\nfunction getIconElements(node: TreeNode): IconElement[] {\n if (node.icon) return [{ tag: 'path', attrs: { d: node.icon } }]\n return typeIcons[node.type || 'custom']\n}\n\nfunction getBadgeContent(node: TreeNode): string | number | undefined {\n if (node.badge !== undefined) return node.badge\n if (props.showCounts && node.children && node.children.length > 0) {\n return node.children.length\n }\n return undefined\n}\n\nfunction getBadgeVariant(node: TreeNode): BadgeVariant {\n return node.badgeVariant || 'default'\n}\n\nfunction hasChildren(node: TreeNode): boolean {\n return !!node.children && node.children.length > 0\n}\n\nfunction canShowChildren(node: TreeNode, depth: number): boolean {\n if (!hasChildren(node)) return false\n if (props.maxDepth !== undefined && depth >= props.maxDepth) return false\n return isExpanded(node.id)\n}\n\nconst treeId = useId()\n\n// Render tree node recursively\nfunction renderNode(node: TreeNode, depth: number): VNode {\n const children = node.children\n const expanded = isExpanded(node.id)\n const canExpand = hasChildren(node)\n const showChildNodes = canShowChildren(node, depth)\n const badge = getBadgeContent(node)\n const badgeVar = getBadgeVariant(node)\n const iconElements = getIconElements(node)\n const selectable = props.selectedId !== undefined\n const selected = selectable && props.selectedId === node.id\n // The child group sits beside the header, so the header treeitem claims it with aria-owns.\n const groupId = `${treeId}-group-${encodeURIComponent(node.id)}`\n\n const header = h(\n 'div',\n {\n class: ['mint-sample-tree__node-header', selected ? 'mint-sample-tree__node-header--selected' : ''],\n // The focusable header is the treeitem, so its selected state is announced on focus.\n role: 'treeitem',\n 'aria-expanded': canExpand ? expanded : undefined,\n 'aria-selected': selectable ? selected : undefined,\n 'aria-owns': showChildNodes ? groupId : undefined,\n tabindex: selectable ? 0 : undefined,\n onClick: () => handleNodeClick(node),\n onKeydown: (e: KeyboardEvent) => handleHeaderKeydown(e, node),\n },\n [\n canExpand\n ? h(\n 'button',\n {\n type: 'button',\n class: 'mint-sample-tree__toggle',\n 'aria-label': expanded ? 'Collapse' : 'Expand',\n onClick: (e: Event) => {\n e.stopPropagation()\n toggleExpand(node)\n },\n },\n [\n h('svg', { fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' }, [\n h('path', { d: 'm9 18 6-6-6-6' }),\n ]),\n ]\n )\n : h('span', { class: 'mint-sample-tree__toggle-placeholder' }),\n props.showIcons\n ? h('span', { class: ['mint-sample-tree__icon', `mint-sample-tree__icon--${node.type || 'custom'}`] }, [\n h('svg', { fill: 'none', stroke: 'currentColor', viewBox: '0 0 24 24', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' },\n iconElements.map(el => h(el.tag, el.attrs))\n ),\n ])\n : null,\n h('span', { class: ['mint-sample-tree__label', `mint-sample-tree__label--${node.type || 'custom'}`] }, node.label),\n badge !== undefined\n ? h('span', { class: ['mint-sample-tree__badge', `mint-sample-tree__badge--${badgeVar}`] }, String(badge))\n : null,\n ]\n )\n\n const childNodes =\n showChildNodes && children\n ? h(\n Transition,\n { enterActiveClass: 'mint-sample-tree__children--entering', leaveActiveClass: 'mint-sample-tree__children--leaving' },\n // The inner wrapper is the single grid row the 0fr → 1fr expand animates (4c).\n () => h('div', { id: groupId, class: 'mint-sample-tree__children', role: 'group' }, [\n h('div', { class: 'mint-sample-tree__children-inner' }, children.map((child) => renderNode(child, depth + 1))),\n ])\n )\n : null\n\n return h(\n 'div',\n {\n key: node.id,\n class: ['mint-sample-tree__node', expanded ? 'mint-sample-tree__node--expanded' : '', !canExpand ? 'mint-sample-tree__node--leaf' : ''],\n },\n [header, childNodes]\n )\n}\n\n// Render the entire tree\nfunction renderTree(): VNode[] {\n return props.nodes.map((node) => renderNode(node, 0))\n}\n</script>\n\n<template>\n <div :class=\"['mint-sample-tree', `mint-sample-tree--${size}`]\" role=\"tree\">\n <!-- Error state -->\n <div v-if=\"error\" class=\"mint-sample-tree__state\">\n <slot name=\"error\" :error=\"error\">\n <DataStateInternal state=\"error\" size=\"sm\" :title=\"error\" />\n </slot>\n </div>\n\n <!-- Loading state -->\n <div v-else-if=\"loading\" class=\"mint-sample-tree__state\">\n <slot name=\"loading\">\n <DataStateInternal state=\"loading\" size=\"sm\" loading-label=\"Loading samples\" />\n </slot>\n </div>\n\n <!-- Empty state -->\n <div v-else-if=\"nodes.length === 0\" class=\"mint-sample-tree__state\">\n <slot name=\"empty\">\n <DataStateInternal state=\"empty\" size=\"sm\" title=\"No items\" :icon-path=\"EMPTY_ICON_PATH\" />\n </slot>\n </div>\n\n <!-- Tree nodes rendered via render function -->\n <template v-else>\n <component :is=\"() => renderTree()\" />\n </template>\n </div>\n</template>\n","import type { ProtocolStepType } from '../types'\n\n// Parameter definition for step templates\nexport interface ParameterDefinition {\n key: string\n label: string\n type: 'number' | 'text' | 'select' | 'concentration' | 'temperature' | 'duration' | 'reagent'\n unit?: string\n options?: Array<{ value: string; label: string }>\n required?: boolean\n default?: unknown\n min?: number\n max?: number\n placeholder?: string\n}\n\n// Step template definition\nexport interface StepTemplate {\n id: string\n type: ProtocolStepType\n name: string\n description?: string\n defaultDuration?: number\n parameters: ParameterDefinition[]\n isBuiltIn?: boolean\n}\n\nexport const BUILT_IN_PROTOCOL_TEMPLATES: StepTemplate[] = [\n {\n id: 'builtin-incubation',\n type: 'incubation',\n name: 'Incubation',\n description: 'Incubate samples at specified conditions',\n defaultDuration: 60,\n isBuiltIn: true,\n parameters: [\n {\n key: 'temperature',\n label: 'Temperature',\n type: 'temperature',\n unit: '°C',\n required: true,\n default: 37,\n min: -80,\n max: 100,\n },\n {\n key: 'duration',\n label: 'Duration',\n type: 'duration',\n unit: 'hours',\n required: true,\n default: 24,\n min: 0,\n },\n {\n key: 'co2',\n label: 'CO2',\n type: 'number',\n unit: '%',\n required: false,\n default: 5,\n min: 0,\n max: 100,\n },\n {\n key: 'humidity',\n label: 'Humidity',\n type: 'number',\n unit: '%',\n required: false,\n default: 95,\n min: 0,\n max: 100,\n },\n ],\n },\n {\n id: 'builtin-wash',\n type: 'wash',\n name: 'Wash',\n description: 'Wash samples with buffer',\n defaultDuration: 5,\n isBuiltIn: true,\n parameters: [\n {\n key: 'buffer',\n label: 'Buffer',\n type: 'select',\n required: true,\n default: 'PBS',\n options: [\n { value: 'PBS', label: 'PBS' },\n { value: 'PBST', label: 'PBST' },\n { value: 'TBS', label: 'TBS' },\n { value: 'TBST', label: 'TBST' },\n { value: 'Water', label: 'Water' },\n { value: 'Other', label: 'Other' },\n ],\n },\n {\n key: 'volume',\n label: 'Volume',\n type: 'number',\n unit: 'µL',\n required: true,\n default: 200,\n min: 0,\n },\n {\n key: 'cycles',\n label: 'Wash Cycles',\n type: 'number',\n required: true,\n default: 3,\n min: 1,\n max: 10,\n },\n ],\n },\n {\n id: 'builtin-addition',\n type: 'addition',\n name: 'Addition',\n description: 'Add reagent or compound to samples',\n defaultDuration: 5,\n isBuiltIn: true,\n parameters: [\n {\n key: 'reagent',\n label: 'Reagent',\n type: 'text',\n required: true,\n placeholder: 'Enter reagent name',\n },\n {\n key: 'volume',\n label: 'Volume',\n type: 'number',\n unit: 'µL',\n required: true,\n default: 100,\n min: 0,\n },\n {\n key: 'concentration',\n label: 'Concentration',\n type: 'concentration',\n required: false,\n },\n ],\n },\n {\n id: 'builtin-measurement',\n type: 'measurement',\n name: 'Measurement',\n description: 'Take measurements using specified instrument',\n defaultDuration: 15,\n isBuiltIn: true,\n parameters: [\n {\n key: 'instrument',\n label: 'Instrument',\n type: 'select',\n required: true,\n options: [\n { value: 'plate_reader', label: 'Plate Reader' },\n { value: 'microscope', label: 'Microscope' },\n { value: 'flow_cytometer', label: 'Flow Cytometer' },\n { value: 'spectrophotometer', label: 'Spectrophotometer' },\n { value: 'other', label: 'Other' },\n ],\n },\n {\n key: 'readout',\n label: 'Readout Type',\n type: 'text',\n required: false,\n placeholder: 'e.g., Absorbance 450nm',\n },\n {\n key: 'notes',\n label: 'Parameters',\n type: 'text',\n required: false,\n placeholder: 'Additional parameters',\n },\n ],\n },\n {\n id: 'builtin-centrifuge',\n type: 'centrifuge',\n name: 'Centrifuge',\n description: 'Centrifuge samples',\n defaultDuration: 10,\n isBuiltIn: true,\n parameters: [\n {\n key: 'speed',\n label: 'Speed',\n type: 'number',\n unit: 'RPM',\n required: true,\n default: 1000,\n min: 100,\n max: 20000,\n },\n {\n key: 'duration',\n label: 'Duration',\n type: 'number',\n unit: 'min',\n required: true,\n default: 5,\n min: 1,\n },\n {\n key: 'temperature',\n label: 'Temperature',\n type: 'temperature',\n unit: '°C',\n required: false,\n default: 4,\n min: -10,\n max: 40,\n },\n ],\n },\n {\n id: 'builtin-transfer',\n type: 'transfer',\n name: 'Transfer',\n description: 'Transfer samples between containers',\n defaultDuration: 10,\n isBuiltIn: true,\n parameters: [\n {\n key: 'source',\n label: 'Source',\n type: 'text',\n required: true,\n placeholder: 'Source container',\n },\n {\n key: 'destination',\n label: 'Destination',\n type: 'text',\n required: true,\n placeholder: 'Destination container',\n },\n {\n key: 'volume',\n label: 'Volume',\n type: 'number',\n unit: 'µL',\n required: true,\n default: 100,\n min: 0,\n },\n ],\n },\n {\n id: 'builtin-mix',\n type: 'mix',\n name: 'Mix',\n description: 'Mix samples',\n defaultDuration: 5,\n isBuiltIn: true,\n parameters: [\n {\n key: 'method',\n label: 'Method',\n type: 'select',\n required: true,\n default: 'pipette',\n options: [\n { value: 'pipette', label: 'Pipette mixing' },\n { value: 'vortex', label: 'Vortex' },\n { value: 'shaker', label: 'Orbital shaker' },\n { value: 'inversion', label: 'Inversion' },\n ],\n },\n {\n key: 'duration',\n label: 'Duration',\n type: 'number',\n unit: 'sec',\n required: false,\n default: 30,\n min: 1,\n },\n {\n key: 'speed',\n label: 'Speed/Intensity',\n type: 'text',\n required: false,\n placeholder: 'e.g., 300 RPM',\n },\n ],\n },\n {\n id: 'builtin-custom',\n type: 'custom',\n name: 'Custom Step',\n description: 'Custom protocol step',\n defaultDuration: 15,\n isBuiltIn: true,\n parameters: [\n {\n key: 'description',\n label: 'Description',\n type: 'text',\n required: true,\n placeholder: 'Describe the step',\n },\n {\n key: 'notes',\n label: 'Notes',\n type: 'text',\n required: false,\n placeholder: 'Additional notes',\n },\n ],\n },\n]\n","import { ref, computed, type Ref, type ComputedRef } from 'vue'\nimport type { ProtocolStep, ProtocolStepType, ProtocolStepStatus } from '../types'\nimport {\n BUILT_IN_PROTOCOL_TEMPLATES,\n type ParameterDefinition,\n type StepTemplate,\n} from './protocolTemplateCatalog'\n\nexport type { ParameterDefinition, StepTemplate } from './protocolTemplateCatalog'\n\n// Validation result\nexport interface ValidationResult {\n valid: boolean\n errors: Record<string, string>\n}\n\nexport interface UseProtocolTemplatesReturn {\n builtInTemplates: StepTemplate[]\n customTemplates: Ref<StepTemplate[]>\n allTemplates: ComputedRef<StepTemplate[]>\n getTemplateByType: (type: ProtocolStepType) => StepTemplate | undefined\n getTemplateById: (id: string) => StepTemplate | undefined\n saveCustomTemplate: (template: StepTemplate) => void\n deleteCustomTemplate: (templateId: string) => void\n validateStep: (step: ProtocolStep, template: StepTemplate) => ValidationResult\n createStepFromTemplate: (template: StepTemplate, overrides?: Partial<ProtocolStep>) => ProtocolStep\n formatParameterValue: (value: unknown, param: ParameterDefinition) => string\n}\n\n// Storage key for custom templates\nconst STORAGE_KEY = 'mint-custom-protocol-templates'\n\nfunction isStepTemplate(value: unknown): value is StepTemplate {\n if (!value || typeof value !== 'object') return false\n const candidate = value as Partial<StepTemplate>\n return (\n typeof candidate.id === 'string'\n && typeof candidate.type === 'string'\n && typeof candidate.name === 'string'\n && Array.isArray(candidate.parameters)\n )\n}\n\nfunction loadCustomTemplates(): StepTemplate[] {\n try {\n const stored = localStorage.getItem(STORAGE_KEY)\n if (stored) {\n const parsed = JSON.parse(stored)\n return Array.isArray(parsed) ? parsed.filter(isStepTemplate) : []\n }\n } catch {\n // Ignore errors\n }\n return []\n}\n\nfunction saveCustomTemplatesToStorage(templates: StepTemplate[]) {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(templates))\n } catch {\n // Ignore errors\n }\n}\n\n/** Provides built-in and custom protocol step templates with validation and step creation helpers. */\nexport function useProtocolTemplates(): UseProtocolTemplatesReturn {\n const customTemplates = ref<StepTemplate[]>(loadCustomTemplates())\n\n const allTemplates = computed(() => {\n return [...BUILT_IN_PROTOCOL_TEMPLATES, ...customTemplates.value]\n })\n\n function getTemplateByType(type: ProtocolStepType): StepTemplate | undefined {\n // First check custom templates, then built-in\n return (\n customTemplates.value.find((t) => t.type === type) ||\n BUILT_IN_PROTOCOL_TEMPLATES.find((t) => t.type === type)\n )\n }\n\n function getTemplateById(id: string): StepTemplate | undefined {\n return (\n customTemplates.value.find((t) => t.id === id) ||\n BUILT_IN_PROTOCOL_TEMPLATES.find((t) => t.id === id)\n )\n }\n\n function saveCustomTemplate(template: StepTemplate) {\n const newTemplate = {\n ...template,\n isBuiltIn: false,\n id: template.id || `custom-${Date.now()}`,\n }\n\n const index = customTemplates.value.findIndex((t) => t.id === newTemplate.id)\n if (index >= 0) {\n customTemplates.value[index] = newTemplate\n } else {\n customTemplates.value.push(newTemplate)\n }\n\n saveCustomTemplatesToStorage(customTemplates.value)\n }\n\n function deleteCustomTemplate(templateId: string) {\n const index = customTemplates.value.findIndex((t) => t.id === templateId)\n if (index >= 0) {\n customTemplates.value.splice(index, 1)\n saveCustomTemplatesToStorage(customTemplates.value)\n }\n }\n\n function validateStep(step: ProtocolStep, template: StepTemplate): ValidationResult {\n const errors: Record<string, string> = {}\n\n if (!step.name || step.name.trim() === '') {\n errors.name = 'Step name is required'\n }\n\n for (const param of template.parameters) {\n if (param.required) {\n const value = step.parameters?.[param.key]\n if (value === undefined || value === null || value === '') {\n errors[param.key] = `${param.label} is required`\n }\n }\n\n if (param.type === 'number' || param.type === 'temperature' || param.type === 'duration') {\n const value = step.parameters?.[param.key]\n if (value !== undefined && value !== null && value !== '') {\n const numValue = Number(value)\n if (isNaN(numValue)) {\n errors[param.key] = `${param.label} must be a number`\n } else {\n if (param.min !== undefined && numValue < param.min) {\n errors[param.key] = `${param.label} must be at least ${param.min}`\n }\n if (param.max !== undefined && numValue > param.max) {\n errors[param.key] = `${param.label} must be at most ${param.max}`\n }\n }\n }\n }\n }\n\n return {\n valid: Object.keys(errors).length === 0,\n errors,\n }\n }\n\n function createStepFromTemplate(\n template: StepTemplate,\n overrides?: Partial<ProtocolStep>\n ): ProtocolStep {\n // Build default parameters from template\n const parameters: Record<string, unknown> = {}\n for (const param of template.parameters) {\n if (param.default !== undefined) {\n parameters[param.key] = param.default\n }\n }\n\n return {\n id: `step-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,\n type: template.type,\n name: template.name,\n description: template.description,\n duration: template.defaultDuration,\n status: 'pending' as ProtocolStepStatus,\n parameters,\n order: 0,\n ...overrides,\n }\n }\n\n function formatParameterValue(value: unknown, param: ParameterDefinition): string {\n if (value === undefined || value === null || value === '') {\n return '-'\n }\n\n if (param.type === 'select' && param.options) {\n const option = param.options.find((o) => o.value === value)\n if (option) return option.label\n }\n\n if (param.type === 'concentration' && typeof value === 'object') {\n const conc = value as { value: number; unit: string }\n return `${conc.value} ${conc.unit}`\n }\n\n const strValue = String(value)\n if (param.unit) {\n return `${strValue} ${param.unit}`\n }\n\n return strValue\n }\n\n return {\n builtInTemplates: BUILT_IN_PROTOCOL_TEMPLATES,\n customTemplates,\n allTemplates,\n getTemplateByType,\n getTemplateById,\n saveCustomTemplate,\n deleteCustomTemplate,\n validateStep,\n createStepFromTemplate,\n formatParameterValue,\n }\n}\n","import type { ProtocolStepType } from '../types'\n\nexport const PROTOCOL_STEP_TYPE_ICONS: Record<ProtocolStepType, string> = {\n incubation: 'M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z',\n wash: 'M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z',\n addition: 'M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z',\n measurement: 'M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z',\n transfer: 'M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4',\n centrifuge: 'M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15',\n mix: 'M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z',\n custom: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z',\n}\n\nexport function formatProtocolDuration(minutes: number | undefined): string {\n if (minutes === undefined) return ''\n if (minutes < 60) return `${minutes}m`\n const hours = Math.floor(minutes / 60)\n const mins = minutes % 60\n return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`\n}\n","<script setup lang=\"ts\">\n/** Renders one typed parameter control for ProtocolStepEditor, composed from SDK fields. */\nimport { computed, useId } from 'vue'\nimport type { ParameterDefinition } from '../composables/useProtocolTemplates'\nimport type { ConcentrationValue } from '../composables/useConcentrationUnits'\nimport BaseInput from './BaseInput.vue'\nimport BaseSelect from './BaseSelect.vue'\nimport NumberInput from './NumberInput.vue'\nimport ConcentrationInput from './ConcentrationInput.vue'\nimport FormField from './FormField.vue'\n\ninterface Props {\n parameter: ParameterDefinition\n modelValue?: unknown\n error?: string\n}\n\nconst props = defineProps<Props>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: unknown]\n}>()\n\nconst controlId = useId()\n\nconst isNumeric = computed(() => ['number', 'temperature', 'duration'].includes(props.parameter.type))\n\nconst scalarValue = computed(() => (\n props.modelValue as string | undefined\n))\n\nconst numericValue = computed(() => (\n typeof props.modelValue === 'number' ? props.modelValue : null\n))\n\nconst concentrationValue = computed(() => (\n props.modelValue as ConcentrationValue | undefined\n))\n\nconst placeholder = computed(() => props.parameter.placeholder\n || (props.parameter.type === 'reagent' ? 'Enter reagent name' : `Enter ${props.parameter.label.toLowerCase()}`))\n\n/** NumberInput reports an empty field as null; an empty parameter is undefined. */\nfunction handleInput(value: string | number | null) {\n emit('update:modelValue', value ?? undefined)\n}\n</script>\n\n<template>\n <FormField\n :label=\"parameter.label\"\n :required=\"parameter.required\"\n :error=\"error\"\n :html-for=\"controlId\"\n layout=\"row\"\n >\n <NumberInput\n v-if=\"isNumeric\"\n :id=\"controlId\"\n size=\"sm\"\n :model-value=\"numericValue\"\n :min=\"parameter.min\"\n :max=\"parameter.max\"\n :unit=\"parameter.unit\"\n :placeholder=\"placeholder\"\n :invalid=\"!!error\"\n @update:model-value=\"handleInput\"\n />\n\n <BaseSelect\n v-else-if=\"parameter.type === 'select'\"\n :id=\"controlId\"\n size=\"sm\"\n :model-value=\"scalarValue\"\n :options=\"parameter.options ?? []\"\n :placeholder=\"`Select ${parameter.label.toLowerCase()}`\"\n :invalid=\"!!error\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n />\n\n <ConcentrationInput\n v-else-if=\"parameter.type === 'concentration'\"\n :model-value=\"concentrationValue\"\n :invalid=\"!!error\"\n size=\"sm\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n />\n\n <BaseInput\n v-else\n :id=\"controlId\"\n size=\"sm\"\n :model-value=\"scalarValue\"\n :placeholder=\"placeholder\"\n :invalid=\"!!error\"\n @update:model-value=\"handleInput\"\n />\n </FormField>\n</template>\n","<script setup lang=\"ts\">\n/** Renders one typed parameter control for ProtocolStepEditor, composed from SDK fields. */\nimport { computed, useId } from 'vue'\nimport type { ParameterDefinition } from '../composables/useProtocolTemplates'\nimport type { ConcentrationValue } from '../composables/useConcentrationUnits'\nimport BaseInput from './BaseInput.vue'\nimport BaseSelect from './BaseSelect.vue'\nimport NumberInput from './NumberInput.vue'\nimport ConcentrationInput from './ConcentrationInput.vue'\nimport FormField from './FormField.vue'\n\ninterface Props {\n parameter: ParameterDefinition\n modelValue?: unknown\n error?: string\n}\n\nconst props = defineProps<Props>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: unknown]\n}>()\n\nconst controlId = useId()\n\nconst isNumeric = computed(() => ['number', 'temperature', 'duration'].includes(props.parameter.type))\n\nconst scalarValue = computed(() => (\n props.modelValue as string | undefined\n))\n\nconst numericValue = computed(() => (\n typeof props.modelValue === 'number' ? props.modelValue : null\n))\n\nconst concentrationValue = computed(() => (\n props.modelValue as ConcentrationValue | undefined\n))\n\nconst placeholder = computed(() => props.parameter.placeholder\n || (props.parameter.type === 'reagent' ? 'Enter reagent name' : `Enter ${props.parameter.label.toLowerCase()}`))\n\n/** NumberInput reports an empty field as null; an empty parameter is undefined. */\nfunction handleInput(value: string | number | null) {\n emit('update:modelValue', value ?? undefined)\n}\n</script>\n\n<template>\n <FormField\n :label=\"parameter.label\"\n :required=\"parameter.required\"\n :error=\"error\"\n :html-for=\"controlId\"\n layout=\"row\"\n >\n <NumberInput\n v-if=\"isNumeric\"\n :id=\"controlId\"\n size=\"sm\"\n :model-value=\"numericValue\"\n :min=\"parameter.min\"\n :max=\"parameter.max\"\n :unit=\"parameter.unit\"\n :placeholder=\"placeholder\"\n :invalid=\"!!error\"\n @update:model-value=\"handleInput\"\n />\n\n <BaseSelect\n v-else-if=\"parameter.type === 'select'\"\n :id=\"controlId\"\n size=\"sm\"\n :model-value=\"scalarValue\"\n :options=\"parameter.options ?? []\"\n :placeholder=\"`Select ${parameter.label.toLowerCase()}`\"\n :invalid=\"!!error\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n />\n\n <ConcentrationInput\n v-else-if=\"parameter.type === 'concentration'\"\n :model-value=\"concentrationValue\"\n :invalid=\"!!error\"\n size=\"sm\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n />\n\n <BaseInput\n v-else\n :id=\"controlId\"\n size=\"sm\"\n :model-value=\"scalarValue\"\n :placeholder=\"placeholder\"\n :invalid=\"!!error\"\n @update:model-value=\"handleInput\"\n />\n </FormField>\n</template>\n","import type { ProtocolStep, ProtocolStepStatus } from '../types'\nimport type { ParameterDefinition, StepTemplate } from '../composables/useProtocolTemplates'\n\nexport interface ProtocolStepEditorFields {\n name: string\n description: string\n duration?: number\n parameters: Record<string, unknown>\n}\n\nexport interface InitializedProtocolStepEditorState {\n selectedTemplateId: string | null\n fields: ProtocolStepEditorFields\n}\n\nexport type ProtocolParameterFormatter = (\n value: unknown,\n parameter: ParameterDefinition\n) => string\n\nexport function buildProtocolTemplateDefaults(\n template: StepTemplate\n): ProtocolStepEditorFields {\n const parameters: Record<string, unknown> = {}\n for (const parameter of template.parameters) {\n if (parameter.default !== undefined) {\n parameters[parameter.key] = parameter.default\n }\n }\n\n return {\n name: template.name,\n description: template.description || '',\n duration: template.defaultDuration,\n parameters,\n }\n}\n\nexport function buildProtocolStepEditorState(\n step: ProtocolStep,\n getTemplateByType: (type: ProtocolStep['type']) => StepTemplate | undefined\n): InitializedProtocolStepEditorState {\n const template = getTemplateByType(step.type)\n\n return {\n selectedTemplateId: template?.id || null,\n fields: {\n name: step.name,\n description: step.description || '',\n duration: step.duration,\n parameters: { ...(step.parameters || {}) },\n },\n }\n}\n\nexport function buildProtocolStepPreview(\n template: StepTemplate,\n fields: ProtocolStepEditorFields,\n sourceStep?: ProtocolStep\n): ProtocolStep {\n return {\n id: sourceStep?.id || 'preview',\n type: template.type,\n name: fields.name || template.name,\n description: fields.description,\n duration: fields.duration ?? template.defaultDuration,\n status: (sourceStep?.status || 'pending') as ProtocolStepStatus,\n parameters: { ...fields.parameters },\n order: sourceStep?.order || 0,\n }\n}\n\nexport function buildProtocolPreviewParams(\n template: StepTemplate,\n parameters: Record<string, unknown>,\n formatParameterValue: ProtocolParameterFormatter\n): string {\n return template.parameters\n .filter((parameter) => (\n parameters[parameter.key] !== undefined\n && parameters[parameter.key] !== ''\n ))\n .map((parameter) => formatParameterValue(parameters[parameter.key], parameter))\n .join(', ')\n}\n\nexport function buildCustomProtocolTemplate(\n template: StepTemplate,\n fields: ProtocolStepEditorFields,\n templateId = `custom-${Date.now()}`\n): StepTemplate {\n return {\n id: templateId,\n type: template.type,\n name: fields.name || template.name,\n description: fields.description,\n defaultDuration: fields.duration,\n parameters: template.parameters.map((parameter) => ({\n ...parameter,\n default: fields.parameters[parameter.key],\n })),\n isBuiltIn: false,\n }\n}\n","<script setup lang=\"ts\">\n/** Form for creating or editing a single protocol step (incubation, wash, addition, centrifuge, etc.) with template picker, typed parameters, and duration. */\nimport { ref, computed, watch, onMounted, useId } from 'vue'\nimport type { ProtocolStep, SelectOption } from '../types'\nimport {\n useProtocolTemplates,\n type StepTemplate,\n} from '../composables/useProtocolTemplates'\nimport {\n PROTOCOL_STEP_TYPE_ICONS as stepTypeIcons,\n formatProtocolDuration as formatDuration,\n} from './ProtocolStep.presentation'\nimport BaseButton from './BaseButton.vue'\nimport BaseInput from './BaseInput.vue'\nimport FormField from './FormField.vue'\nimport ProtocolStepParameterField from './ProtocolStepParameterField.vue'\nimport BaseSelect from './BaseSelect.vue'\nimport {\n buildCustomProtocolTemplate,\n buildProtocolPreviewParams,\n buildProtocolStepEditorState,\n buildProtocolStepPreview,\n buildProtocolTemplateDefaults,\n type ProtocolStepEditorFields,\n} from './ProtocolStepEditor.state'\n\ninterface Props {\n modelValue?: ProtocolStep\n templates?: StepTemplate[]\n customTemplates?: StepTemplate[]\n mode?: 'create' | 'edit'\n showPreview?: boolean\n /** 1-based position of this step, shown as \"3 / 7\" beside the title together with stepCount. */\n stepNumber?: number\n /** Total steps in the protocol; the ordinal shows only when both are set. */\n stepCount?: number\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n mode: 'create',\n showPreview: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [step: ProtocolStep]\n 'save': [step: ProtocolStep]\n 'save-template': [template: StepTemplate]\n 'cancel': []\n}>()\n\nconst {\n allTemplates,\n getTemplateByType,\n validateStep,\n formatParameterValue,\n} = useProtocolTemplates()\n\n// State\nconst fieldId = useId()\nconst selectedTemplateId = ref<string | null>(null)\nconst stepName = ref('')\nconst stepDescription = ref('')\nconst stepDuration = ref<number | undefined>()\nconst parameters = ref<Record<string, unknown>>({})\nconst validationErrors = ref<Record<string, string>>({})\n\n// Combined templates from props and composable\nconst availableTemplates = computed(() => {\n if (props.templates && props.templates.length > 0) {\n return props.templates\n }\n const combined = [...allTemplates.value]\n if (props.customTemplates) {\n combined.push(...props.customTemplates)\n }\n return combined\n})\n\nconst templateOptions = computed<SelectOption<string>[]>(() =>\n availableTemplates.value.map((t) => ({ value: t.id, label: t.name, description: t.description })),\n)\n\n// Selected template\nconst selectedTemplate = computed(() => {\n if (!selectedTemplateId.value) return null\n return availableTemplates.value.find((t) => t.id === selectedTemplateId.value) || null\n})\n\n// Preview computed\nconst previewStep = computed<ProtocolStep | null>(() => {\n if (!selectedTemplate.value) return null\n return buildProtocolStepPreview(selectedTemplate.value, editorFields(), props.modelValue)\n})\n\nconst previewParams = computed(() => {\n if (!selectedTemplate.value || !previewStep.value) return ''\n return buildProtocolPreviewParams(\n selectedTemplate.value,\n parameters.value,\n formatParameterValue\n )\n})\n\nfunction editorFields(): ProtocolStepEditorFields {\n return {\n name: stepName.value,\n description: stepDescription.value,\n duration: stepDuration.value,\n parameters: parameters.value,\n }\n}\n\nfunction applyEditorFields(fields: ProtocolStepEditorFields) {\n stepName.value = fields.name\n stepDescription.value = fields.description\n stepDuration.value = fields.duration\n parameters.value = { ...fields.parameters }\n}\n\nfunction initFromStep(step: ProtocolStep) {\n const state = buildProtocolStepEditorState(step, getTemplateByType)\n selectedTemplateId.value = state.selectedTemplateId\n applyEditorFields(state.fields)\n}\n\n// Handle template selection\nfunction selectTemplate(template: StepTemplate) {\n selectedTemplateId.value = template.id\n\n // Reset to template defaults if creating new\n if (props.mode === 'create') {\n applyEditorFields(buildProtocolTemplateDefaults(template))\n }\n}\n\nfunction handleTemplateChange(id: string | number) {\n const template = availableTemplates.value.find((t) => t.id === id)\n if (template) selectTemplate(template)\n}\n\n// Handle parameter change\nfunction handleParamChange(key: string, value: unknown) {\n parameters.value[key] = value\n // Clear validation error when user changes value\n if (validationErrors.value[key]) {\n delete validationErrors.value[key]\n }\n}\n\n// Validate and save\nfunction handleSave() {\n if (!selectedTemplate.value || !previewStep.value) return\n\n const result = validateStep(previewStep.value, selectedTemplate.value)\n validationErrors.value = result.errors\n\n if (result.valid) {\n emit('save', previewStep.value)\n emit('update:modelValue', previewStep.value)\n }\n}\n\n// Save as template\nfunction handleSaveTemplate() {\n if (!selectedTemplate.value) return\n emit('save-template', buildCustomProtocolTemplate(selectedTemplate.value, editorFields()))\n}\n\n// Handle cancel\nfunction handleCancel() {\n emit('cancel')\n}\n\nonMounted(() => {\n if (props.modelValue) {\n initFromStep(props.modelValue)\n } else if (availableTemplates.value.length > 0) {\n selectTemplate(availableTemplates.value[0])\n }\n})\n\n// Watch for external changes\nwatch(\n () => props.modelValue,\n (step) => {\n if (step) {\n initFromStep(step)\n }\n }\n)\n</script>\n\n<template>\n <div class=\"mint-protocol-editor\">\n <!-- Header: the mode is part of the title -->\n <div class=\"mint-protocol-editor__header\">\n <span class=\"mint-protocol-editor__title\">\n {{ mode === 'create' ? 'Create step' : 'Edit step' }}\n </span>\n <span v-if=\"stepNumber && stepCount\" class=\"mint-protocol-editor__counter\">{{ stepNumber }} / {{ stepCount }}</span>\n </div>\n\n <!-- Form -->\n <div class=\"mint-protocol-editor__form\">\n <FormField label=\"Template\" :field-id=\"`${fieldId}-template`\" layout=\"row\">\n <BaseSelect\n searchable\n size=\"sm\"\n :aria-labelledby=\"`${fieldId}-template-label`\"\n :model-value=\"selectedTemplateId ?? undefined\"\n :options=\"templateOptions\"\n placeholder=\"Select template…\"\n search-placeholder=\"Search templates…\"\n @update:model-value=\"handleTemplateChange\"\n />\n </FormField>\n\n <FormField label=\"Step name\" required :error=\"validationErrors.name\" :html-for=\"`${fieldId}-name`\" layout=\"row\">\n <BaseInput\n :id=\"`${fieldId}-name`\"\n v-model=\"stepName\"\n size=\"sm\"\n placeholder=\"Enter step name\"\n :invalid=\"!!validationErrors.name\"\n />\n </FormField>\n\n <!-- Dynamic parameters -->\n <template v-if=\"selectedTemplate\">\n <ProtocolStepParameterField\n v-for=\"param in selectedTemplate.parameters\"\n :key=\"param.key\"\n :parameter=\"param\"\n :model-value=\"parameters[param.key]\"\n :error=\"validationErrors[param.key]\"\n @update:model-value=\"handleParamChange(param.key, $event)\"\n />\n </template>\n\n <FormField label=\"Description\" :html-for=\"`${fieldId}-description`\" layout=\"row\">\n <BaseInput\n type=\"textarea\"\n :id=\"`${fieldId}-description`\"\n v-model=\"stepDescription\"\n placeholder=\"Optional step description\"\n :rows=\"2\"\n />\n </FormField>\n </div>\n\n <!-- Preview -->\n <div v-if=\"showPreview && previewStep\" class=\"mint-protocol-editor__preview\">\n <div class=\"mint-protocol-editor__preview-title\">Preview</div>\n <div class=\"mint-protocol-editor__preview-card\">\n <div class=\"mint-protocol-editor__preview-icon\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n :d=\"stepTypeIcons[previewStep.type]\"\n />\n </svg>\n </div>\n <div class=\"mint-protocol-editor__preview-content\">\n <div class=\"mint-protocol-editor__preview-name\">{{ previewStep.name }}</div>\n <div v-if=\"previewParams\" class=\"mint-protocol-editor__preview-params\">\n {{ previewParams }}\n </div>\n </div>\n <div v-if=\"previewStep.duration\" class=\"mint-protocol-editor__preview-duration\">\n {{ formatDuration(previewStep.duration) }}\n </div>\n </div>\n </div>\n\n <!-- Actions -->\n <div class=\"mint-protocol-editor__actions\">\n <BaseButton variant=\"ghost\" size=\"sm\" @click=\"handleSaveTemplate\">\n Save as template\n </BaseButton>\n <div class=\"mint-protocol-editor__actions-spacer\" />\n <BaseButton variant=\"secondary\" size=\"sm\" @click=\"handleCancel\">\n Cancel\n </BaseButton>\n <BaseButton variant=\"primary\" size=\"sm\" :disabled=\"!selectedTemplate\" @click=\"handleSave\">\n {{ mode === 'create' ? 'Add step' : 'Save changes' }}\n </BaseButton>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Form for creating or editing a single protocol step (incubation, wash, addition, centrifuge, etc.) with template picker, typed parameters, and duration. */\nimport { ref, computed, watch, onMounted, useId } from 'vue'\nimport type { ProtocolStep, SelectOption } from '../types'\nimport {\n useProtocolTemplates,\n type StepTemplate,\n} from '../composables/useProtocolTemplates'\nimport {\n PROTOCOL_STEP_TYPE_ICONS as stepTypeIcons,\n formatProtocolDuration as formatDuration,\n} from './ProtocolStep.presentation'\nimport BaseButton from './BaseButton.vue'\nimport BaseInput from './BaseInput.vue'\nimport FormField from './FormField.vue'\nimport ProtocolStepParameterField from './ProtocolStepParameterField.vue'\nimport BaseSelect from './BaseSelect.vue'\nimport {\n buildCustomProtocolTemplate,\n buildProtocolPreviewParams,\n buildProtocolStepEditorState,\n buildProtocolStepPreview,\n buildProtocolTemplateDefaults,\n type ProtocolStepEditorFields,\n} from './ProtocolStepEditor.state'\n\ninterface Props {\n modelValue?: ProtocolStep\n templates?: StepTemplate[]\n customTemplates?: StepTemplate[]\n mode?: 'create' | 'edit'\n showPreview?: boolean\n /** 1-based position of this step, shown as \"3 / 7\" beside the title together with stepCount. */\n stepNumber?: number\n /** Total steps in the protocol; the ordinal shows only when both are set. */\n stepCount?: number\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n mode: 'create',\n showPreview: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [step: ProtocolStep]\n 'save': [step: ProtocolStep]\n 'save-template': [template: StepTemplate]\n 'cancel': []\n}>()\n\nconst {\n allTemplates,\n getTemplateByType,\n validateStep,\n formatParameterValue,\n} = useProtocolTemplates()\n\n// State\nconst fieldId = useId()\nconst selectedTemplateId = ref<string | null>(null)\nconst stepName = ref('')\nconst stepDescription = ref('')\nconst stepDuration = ref<number | undefined>()\nconst parameters = ref<Record<string, unknown>>({})\nconst validationErrors = ref<Record<string, string>>({})\n\n// Combined templates from props and composable\nconst availableTemplates = computed(() => {\n if (props.templates && props.templates.length > 0) {\n return props.templates\n }\n const combined = [...allTemplates.value]\n if (props.customTemplates) {\n combined.push(...props.customTemplates)\n }\n return combined\n})\n\nconst templateOptions = computed<SelectOption<string>[]>(() =>\n availableTemplates.value.map((t) => ({ value: t.id, label: t.name, description: t.description })),\n)\n\n// Selected template\nconst selectedTemplate = computed(() => {\n if (!selectedTemplateId.value) return null\n return availableTemplates.value.find((t) => t.id === selectedTemplateId.value) || null\n})\n\n// Preview computed\nconst previewStep = computed<ProtocolStep | null>(() => {\n if (!selectedTemplate.value) return null\n return buildProtocolStepPreview(selectedTemplate.value, editorFields(), props.modelValue)\n})\n\nconst previewParams = computed(() => {\n if (!selectedTemplate.value || !previewStep.value) return ''\n return buildProtocolPreviewParams(\n selectedTemplate.value,\n parameters.value,\n formatParameterValue\n )\n})\n\nfunction editorFields(): ProtocolStepEditorFields {\n return {\n name: stepName.value,\n description: stepDescription.value,\n duration: stepDuration.value,\n parameters: parameters.value,\n }\n}\n\nfunction applyEditorFields(fields: ProtocolStepEditorFields) {\n stepName.value = fields.name\n stepDescription.value = fields.description\n stepDuration.value = fields.duration\n parameters.value = { ...fields.parameters }\n}\n\nfunction initFromStep(step: ProtocolStep) {\n const state = buildProtocolStepEditorState(step, getTemplateByType)\n selectedTemplateId.value = state.selectedTemplateId\n applyEditorFields(state.fields)\n}\n\n// Handle template selection\nfunction selectTemplate(template: StepTemplate) {\n selectedTemplateId.value = template.id\n\n // Reset to template defaults if creating new\n if (props.mode === 'create') {\n applyEditorFields(buildProtocolTemplateDefaults(template))\n }\n}\n\nfunction handleTemplateChange(id: string | number) {\n const template = availableTemplates.value.find((t) => t.id === id)\n if (template) selectTemplate(template)\n}\n\n// Handle parameter change\nfunction handleParamChange(key: string, value: unknown) {\n parameters.value[key] = value\n // Clear validation error when user changes value\n if (validationErrors.value[key]) {\n delete validationErrors.value[key]\n }\n}\n\n// Validate and save\nfunction handleSave() {\n if (!selectedTemplate.value || !previewStep.value) return\n\n const result = validateStep(previewStep.value, selectedTemplate.value)\n validationErrors.value = result.errors\n\n if (result.valid) {\n emit('save', previewStep.value)\n emit('update:modelValue', previewStep.value)\n }\n}\n\n// Save as template\nfunction handleSaveTemplate() {\n if (!selectedTemplate.value) return\n emit('save-template', buildCustomProtocolTemplate(selectedTemplate.value, editorFields()))\n}\n\n// Handle cancel\nfunction handleCancel() {\n emit('cancel')\n}\n\nonMounted(() => {\n if (props.modelValue) {\n initFromStep(props.modelValue)\n } else if (availableTemplates.value.length > 0) {\n selectTemplate(availableTemplates.value[0])\n }\n})\n\n// Watch for external changes\nwatch(\n () => props.modelValue,\n (step) => {\n if (step) {\n initFromStep(step)\n }\n }\n)\n</script>\n\n<template>\n <div class=\"mint-protocol-editor\">\n <!-- Header: the mode is part of the title -->\n <div class=\"mint-protocol-editor__header\">\n <span class=\"mint-protocol-editor__title\">\n {{ mode === 'create' ? 'Create step' : 'Edit step' }}\n </span>\n <span v-if=\"stepNumber && stepCount\" class=\"mint-protocol-editor__counter\">{{ stepNumber }} / {{ stepCount }}</span>\n </div>\n\n <!-- Form -->\n <div class=\"mint-protocol-editor__form\">\n <FormField label=\"Template\" :field-id=\"`${fieldId}-template`\" layout=\"row\">\n <BaseSelect\n searchable\n size=\"sm\"\n :aria-labelledby=\"`${fieldId}-template-label`\"\n :model-value=\"selectedTemplateId ?? undefined\"\n :options=\"templateOptions\"\n placeholder=\"Select template…\"\n search-placeholder=\"Search templates…\"\n @update:model-value=\"handleTemplateChange\"\n />\n </FormField>\n\n <FormField label=\"Step name\" required :error=\"validationErrors.name\" :html-for=\"`${fieldId}-name`\" layout=\"row\">\n <BaseInput\n :id=\"`${fieldId}-name`\"\n v-model=\"stepName\"\n size=\"sm\"\n placeholder=\"Enter step name\"\n :invalid=\"!!validationErrors.name\"\n />\n </FormField>\n\n <!-- Dynamic parameters -->\n <template v-if=\"selectedTemplate\">\n <ProtocolStepParameterField\n v-for=\"param in selectedTemplate.parameters\"\n :key=\"param.key\"\n :parameter=\"param\"\n :model-value=\"parameters[param.key]\"\n :error=\"validationErrors[param.key]\"\n @update:model-value=\"handleParamChange(param.key, $event)\"\n />\n </template>\n\n <FormField label=\"Description\" :html-for=\"`${fieldId}-description`\" layout=\"row\">\n <BaseInput\n type=\"textarea\"\n :id=\"`${fieldId}-description`\"\n v-model=\"stepDescription\"\n placeholder=\"Optional step description\"\n :rows=\"2\"\n />\n </FormField>\n </div>\n\n <!-- Preview -->\n <div v-if=\"showPreview && previewStep\" class=\"mint-protocol-editor__preview\">\n <div class=\"mint-protocol-editor__preview-title\">Preview</div>\n <div class=\"mint-protocol-editor__preview-card\">\n <div class=\"mint-protocol-editor__preview-icon\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n :d=\"stepTypeIcons[previewStep.type]\"\n />\n </svg>\n </div>\n <div class=\"mint-protocol-editor__preview-content\">\n <div class=\"mint-protocol-editor__preview-name\">{{ previewStep.name }}</div>\n <div v-if=\"previewParams\" class=\"mint-protocol-editor__preview-params\">\n {{ previewParams }}\n </div>\n </div>\n <div v-if=\"previewStep.duration\" class=\"mint-protocol-editor__preview-duration\">\n {{ formatDuration(previewStep.duration) }}\n </div>\n </div>\n </div>\n\n <!-- Actions -->\n <div class=\"mint-protocol-editor__actions\">\n <BaseButton variant=\"ghost\" size=\"sm\" @click=\"handleSaveTemplate\">\n Save as template\n </BaseButton>\n <div class=\"mint-protocol-editor__actions-spacer\" />\n <BaseButton variant=\"secondary\" size=\"sm\" @click=\"handleCancel\">\n Cancel\n </BaseButton>\n <BaseButton variant=\"primary\" size=\"sm\" :disabled=\"!selectedTemplate\" @click=\"handleSave\">\n {{ mode === 'create' ? 'Add step' : 'Save changes' }}\n </BaseButton>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Read-only display of a numeric measurement in plain, scientific (×10ⁿ), or SI-prefix compact notation with optional unit and copy-to-clipboard. */\nimport { computed, ref } from 'vue'\nimport type { NumberNotation } from '../types'\n\ninterface Props {\n value: number\n precision?: number\n notation?: NumberNotation\n unit?: string\n copyable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n precision: 3,\n notation: 'auto',\n copyable: false,\n})\n\nconst copied = ref(false)\nlet copiedTimer: ReturnType<typeof setTimeout> | undefined\n\nconst SI_SUFFIXES: [number, string][] = [\n [1e12, 'T'],\n [1e9, 'G'],\n [1e6, 'M'],\n [1e3, 'k'],\n [1e-3, 'm'],\n [1e-6, '\\u00B5'],\n [1e-9, 'n'],\n [1e-12, 'p'],\n]\n\nfunction roundToPrecision(n: number, digits: number): string {\n return n.toFixed(digits).replace(/\\.?0+$/, '') || '0'\n}\n\ninterface FormattedNumber {\n type: 'plain' | 'scientific' | 'compact'\n mantissa?: string\n exponent?: number\n exponentStr?: string\n plain?: string\n suffix?: string\n}\n\nconst formatted = computed<FormattedNumber>(() => {\n const v = props.value\n\n if (!Number.isFinite(v)) return { type: 'plain', plain: '' }\n if (v === 0) return { type: 'plain', plain: '0' }\n\n const absV = Math.abs(v)\n\n if (props.notation === 'compact') {\n for (const [threshold, suffix] of SI_SUFFIXES) {\n if (absV >= threshold * 0.999) {\n return {\n type: 'compact',\n plain: roundToPrecision(v / threshold, props.precision),\n suffix,\n }\n }\n }\n return { type: 'plain', plain: roundToPrecision(v, props.precision) }\n }\n\n const useScientific =\n props.notation === 'scientific' ||\n props.notation === 'engineering' ||\n (props.notation === 'auto' && (absV < 0.01 || absV >= 10000))\n\n if (!useScientific) {\n return { type: 'plain', plain: roundToPrecision(v, props.precision) }\n }\n\n let exp = Math.floor(Math.log10(absV))\n\n if (props.notation === 'engineering') {\n exp = Math.floor(exp / 3) * 3\n }\n\n const mantissa = v / Math.pow(10, exp)\n const mantissaStr = roundToPrecision(mantissa, props.precision)\n\n return {\n type: 'scientific',\n mantissa: mantissaStr,\n exponent: exp,\n exponentStr: String(exp).replace('-', '\\u2212'), // raised by CSS; true minus sign\n }\n})\n\nconst specialDisplay = computed(() => {\n if (Number.isNaN(props.value)) return 'NaN'\n if (props.value === Infinity) return '\\u221E'\n if (props.value === -Infinity) return '-\\u221E'\n return null\n})\n\nconst plainTextValue = computed(() => {\n if (specialDisplay.value) return specialDisplay.value\n const f = formatted.value\n let text: string\n if (f.type === 'scientific') {\n text = `${f.mantissa}e${f.exponent}`\n } else if (f.type === 'compact') {\n text = `${f.plain}${f.suffix ?? ''}`\n } else {\n text = f.plain ?? '0'\n }\n if (props.unit) text += ` ${props.unit}`\n return text\n})\n\nconst rootEl = ref<HTMLElement>()\n\n/** A selection inside the number copies the same canonical string as the button; a wider one is left to the browser. */\nfunction onSelectionCopy(event: ClipboardEvent) {\n const selection = document.getSelection()\n const root = rootEl.value\n if (!selection || selection.isCollapsed || !root?.contains(selection.anchorNode) || !root.contains(selection.focusNode)) return\n event.clipboardData?.setData('text/plain', plainTextValue.value)\n event.preventDefault()\n}\n\nasync function copyToClipboard() {\n try {\n await navigator.clipboard.writeText(plainTextValue.value)\n copied.value = true\n clearTimeout(copiedTimer)\n copiedTimer = setTimeout(() => { copied.value = false }, 1500)\n } catch {\n // clipboard not available\n }\n}\n</script>\n\n<template>\n <span ref=\"rootEl\" class=\"mint-sci-number\" @copy=\"onSelectionCopy\">\n <!-- Special values: NaN, Infinity -->\n <template v-if=\"specialDisplay\">\n <span class=\"mint-sci-number__plain\">{{ specialDisplay }}</span>\n </template>\n\n <!-- Scientific / Engineering notation -->\n <template v-else-if=\"formatted.type === 'scientific'\">\n <span class=\"mint-sci-number__mantissa\">{{ formatted.mantissa }}</span>\n <span class=\"mint-sci-number__times\">×</span>\n <span class=\"mint-sci-number__base\">10</span>\n <!-- The raised digits are CSS-generated so no selection copies \"1023\"; the hidden \"^n\" carries the value with an ASCII minus. -->\n <span class=\"mint-sci-number__sr\">^{{ formatted.exponent }}</span>\n <span class=\"mint-sci-number__exponent\" :data-exponent=\"formatted.exponentStr\" aria-hidden=\"true\" />\n </template>\n\n <!-- Compact notation with SI suffix -->\n <template v-else-if=\"formatted.type === 'compact'\">\n <span class=\"mint-sci-number__plain\">{{ formatted.plain }}</span>\n <span v-if=\"formatted.suffix\" class=\"mint-sci-number__suffix\">{{ formatted.suffix }}</span>\n </template>\n\n <!-- Plain number -->\n <template v-else>\n <span class=\"mint-sci-number__plain\">{{ formatted.plain }}</span>\n </template>\n\n <!-- Unit -->\n <span v-if=\"unit\" class=\"mint-sci-number__unit\">{{ unit }}</span>\n\n <!-- Copy button -->\n <button\n v-if=\"copyable\"\n :class=\"['mint-sci-number__copy-btn', { 'mint-sci-number__copy-btn--copied': copied }]\"\n type=\"button\"\n title=\"Copy value\"\n aria-label=\"Copy value\"\n @click.stop=\"copyToClipboard\"\n >\n <svg v-if=\"!copied\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\" />\n <path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\" />\n </svg>\n <svg v-else width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n </button>\n <span v-if=\"copied\" class=\"mint-sci-number__copied\" role=\"status\">Copied {{ plainTextValue }}</span>\n </span>\n</template>\n","<script setup lang=\"ts\">\n/** Read-only display of a numeric measurement in plain, scientific (×10ⁿ), or SI-prefix compact notation with optional unit and copy-to-clipboard. */\nimport { computed, ref } from 'vue'\nimport type { NumberNotation } from '../types'\n\ninterface Props {\n value: number\n precision?: number\n notation?: NumberNotation\n unit?: string\n copyable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n precision: 3,\n notation: 'auto',\n copyable: false,\n})\n\nconst copied = ref(false)\nlet copiedTimer: ReturnType<typeof setTimeout> | undefined\n\nconst SI_SUFFIXES: [number, string][] = [\n [1e12, 'T'],\n [1e9, 'G'],\n [1e6, 'M'],\n [1e3, 'k'],\n [1e-3, 'm'],\n [1e-6, '\\u00B5'],\n [1e-9, 'n'],\n [1e-12, 'p'],\n]\n\nfunction roundToPrecision(n: number, digits: number): string {\n return n.toFixed(digits).replace(/\\.?0+$/, '') || '0'\n}\n\ninterface FormattedNumber {\n type: 'plain' | 'scientific' | 'compact'\n mantissa?: string\n exponent?: number\n exponentStr?: string\n plain?: string\n suffix?: string\n}\n\nconst formatted = computed<FormattedNumber>(() => {\n const v = props.value\n\n if (!Number.isFinite(v)) return { type: 'plain', plain: '' }\n if (v === 0) return { type: 'plain', plain: '0' }\n\n const absV = Math.abs(v)\n\n if (props.notation === 'compact') {\n for (const [threshold, suffix] of SI_SUFFIXES) {\n if (absV >= threshold * 0.999) {\n return {\n type: 'compact',\n plain: roundToPrecision(v / threshold, props.precision),\n suffix,\n }\n }\n }\n return { type: 'plain', plain: roundToPrecision(v, props.precision) }\n }\n\n const useScientific =\n props.notation === 'scientific' ||\n props.notation === 'engineering' ||\n (props.notation === 'auto' && (absV < 0.01 || absV >= 10000))\n\n if (!useScientific) {\n return { type: 'plain', plain: roundToPrecision(v, props.precision) }\n }\n\n let exp = Math.floor(Math.log10(absV))\n\n if (props.notation === 'engineering') {\n exp = Math.floor(exp / 3) * 3\n }\n\n const mantissa = v / Math.pow(10, exp)\n const mantissaStr = roundToPrecision(mantissa, props.precision)\n\n return {\n type: 'scientific',\n mantissa: mantissaStr,\n exponent: exp,\n exponentStr: String(exp).replace('-', '\\u2212'), // raised by CSS; true minus sign\n }\n})\n\nconst specialDisplay = computed(() => {\n if (Number.isNaN(props.value)) return 'NaN'\n if (props.value === Infinity) return '\\u221E'\n if (props.value === -Infinity) return '-\\u221E'\n return null\n})\n\nconst plainTextValue = computed(() => {\n if (specialDisplay.value) return specialDisplay.value\n const f = formatted.value\n let text: string\n if (f.type === 'scientific') {\n text = `${f.mantissa}e${f.exponent}`\n } else if (f.type === 'compact') {\n text = `${f.plain}${f.suffix ?? ''}`\n } else {\n text = f.plain ?? '0'\n }\n if (props.unit) text += ` ${props.unit}`\n return text\n})\n\nconst rootEl = ref<HTMLElement>()\n\n/** A selection inside the number copies the same canonical string as the button; a wider one is left to the browser. */\nfunction onSelectionCopy(event: ClipboardEvent) {\n const selection = document.getSelection()\n const root = rootEl.value\n if (!selection || selection.isCollapsed || !root?.contains(selection.anchorNode) || !root.contains(selection.focusNode)) return\n event.clipboardData?.setData('text/plain', plainTextValue.value)\n event.preventDefault()\n}\n\nasync function copyToClipboard() {\n try {\n await navigator.clipboard.writeText(plainTextValue.value)\n copied.value = true\n clearTimeout(copiedTimer)\n copiedTimer = setTimeout(() => { copied.value = false }, 1500)\n } catch {\n // clipboard not available\n }\n}\n</script>\n\n<template>\n <span ref=\"rootEl\" class=\"mint-sci-number\" @copy=\"onSelectionCopy\">\n <!-- Special values: NaN, Infinity -->\n <template v-if=\"specialDisplay\">\n <span class=\"mint-sci-number__plain\">{{ specialDisplay }}</span>\n </template>\n\n <!-- Scientific / Engineering notation -->\n <template v-else-if=\"formatted.type === 'scientific'\">\n <span class=\"mint-sci-number__mantissa\">{{ formatted.mantissa }}</span>\n <span class=\"mint-sci-number__times\">×</span>\n <span class=\"mint-sci-number__base\">10</span>\n <!-- The raised digits are CSS-generated so no selection copies \"1023\"; the hidden \"^n\" carries the value with an ASCII minus. -->\n <span class=\"mint-sci-number__sr\">^{{ formatted.exponent }}</span>\n <span class=\"mint-sci-number__exponent\" :data-exponent=\"formatted.exponentStr\" aria-hidden=\"true\" />\n </template>\n\n <!-- Compact notation with SI suffix -->\n <template v-else-if=\"formatted.type === 'compact'\">\n <span class=\"mint-sci-number__plain\">{{ formatted.plain }}</span>\n <span v-if=\"formatted.suffix\" class=\"mint-sci-number__suffix\">{{ formatted.suffix }}</span>\n </template>\n\n <!-- Plain number -->\n <template v-else>\n <span class=\"mint-sci-number__plain\">{{ formatted.plain }}</span>\n </template>\n\n <!-- Unit -->\n <span v-if=\"unit\" class=\"mint-sci-number__unit\">{{ unit }}</span>\n\n <!-- Copy button -->\n <button\n v-if=\"copyable\"\n :class=\"['mint-sci-number__copy-btn', { 'mint-sci-number__copy-btn--copied': copied }]\"\n type=\"button\"\n title=\"Copy value\"\n aria-label=\"Copy value\"\n @click.stop=\"copyToClipboard\"\n >\n <svg v-if=\"!copied\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\" />\n <path d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\" />\n </svg>\n <svg v-else width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\" />\n </svg>\n </button>\n <span v-if=\"copied\" class=\"mint-sci-number__copied\" role=\"status\">Copied {{ plainTextValue }}</span>\n </span>\n</template>\n","<script setup lang=\"ts\">\n/** Compact, overflow-safe display for mass-spectrometry adduct notation. */\nimport { computed } from 'vue'\n\ninterface Props {\n value?: string | null\n emptyText?: string\n variant?: 'inline' | 'badge'\n tone?: 'default' | 'on-primary' | 'muted'\n size?: 'xs' | 'inherit'\n font?: 'mono' | 'sans'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n value: null,\n emptyText: '-',\n variant: 'inline',\n tone: 'default',\n size: 'xs',\n font: 'sans',\n})\n\nconst value = computed(() => props.value?.trim() ?? '')\n// `[M+2H]2+` → base `[M+2H]`, charge `2+`; anything unbracketed stays one run.\nconst parts = computed(() => {\n const match = /^(.*\\])(\\d*[+\\-−]+\\d*)$/.exec(value.value)\n return match ? { base: match[1], charge: match[2] } : { base: value.value, charge: '' }\n})\n</script>\n\n<template>\n <span\n :class=\"[\n 'mint-adduct-text',\n `mint-adduct-text--${variant}`,\n `mint-adduct-text--${tone}`,\n `mint-adduct-text--${size}`,\n `mint-adduct-text--${font}`,\n { 'mint-adduct-text--empty': !value },\n ]\"\n :title=\"value || undefined\"\n >\n <template v-if=\"value\">{{ parts.base }}<sup v-if=\"parts.charge\" class=\"mint-adduct-text__charge\">{{ parts.charge }}</sup></template>\n <template v-else>{{ emptyText }}</template>\n </span>\n</template>\n","<script setup lang=\"ts\">\n/** Compact, overflow-safe display for mass-spectrometry adduct notation. */\nimport { computed } from 'vue'\n\ninterface Props {\n value?: string | null\n emptyText?: string\n variant?: 'inline' | 'badge'\n tone?: 'default' | 'on-primary' | 'muted'\n size?: 'xs' | 'inherit'\n font?: 'mono' | 'sans'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n value: null,\n emptyText: '-',\n variant: 'inline',\n tone: 'default',\n size: 'xs',\n font: 'sans',\n})\n\nconst value = computed(() => props.value?.trim() ?? '')\n// `[M+2H]2+` → base `[M+2H]`, charge `2+`; anything unbracketed stays one run.\nconst parts = computed(() => {\n const match = /^(.*\\])(\\d*[+\\-−]+\\d*)$/.exec(value.value)\n return match ? { base: match[1], charge: match[2] } : { base: value.value, charge: '' }\n})\n</script>\n\n<template>\n <span\n :class=\"[\n 'mint-adduct-text',\n `mint-adduct-text--${variant}`,\n `mint-adduct-text--${tone}`,\n `mint-adduct-text--${size}`,\n `mint-adduct-text--${font}`,\n { 'mint-adduct-text--empty': !value },\n ]\"\n :title=\"value || undefined\"\n >\n <template v-if=\"value\">{{ parts.base }}<sup v-if=\"parts.charge\" class=\"mint-adduct-text__charge\">{{ parts.charge }}</sup></template>\n <template v-else>{{ emptyText }}</template>\n </span>\n</template>\n","<script setup lang=\"ts\">\n/** Renders a chemical formula and optional MS adduct with typographically correct subscripts and charges. */\nimport { computed } from 'vue'\nimport { renderFormulaParts, type FormulaPart } from '../utils/formula'\nimport AdductText from './AdductText.vue'\n\ninterface Props {\n formula?: string | null\n inline?: boolean\n adduct?: string | null\n emptyText?: string\n pill?: boolean\n size?: 'xs' | 'sm' | 'inherit'\n tone?: 'primary' | 'secondary' | 'muted'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n formula: null,\n inline: true,\n adduct: null,\n emptyText: '-',\n pill: false,\n size: 'inherit',\n tone: 'primary',\n})\n\nconst formula = computed(() => props.formula?.trim() ?? '')\nconst title = computed(() => [formula.value, props.adduct?.trim()].filter(Boolean).join(' '))\n\nfunction getParts(f: string): FormulaPart[] {\n return renderFormulaParts(f)\n}\n</script>\n\n<template>\n <component\n :is=\"inline ? 'span' : 'div'\"\n :class=\"[\n 'mint-chem-formula',\n !inline ? 'mint-chem-formula--block' : '',\n `mint-chem-formula--${size}`,\n `mint-chem-formula--${tone}`,\n { 'mint-chem-formula--pill': pill, 'mint-chem-formula--empty': !formula },\n ]\"\n :title=\"title || undefined\"\n >\n <template v-if=\"formula\">\n <span class=\"mint-chem-formula__formula\">\n <template v-for=\"(part, i) in getParts(formula)\" :key=\"i\">\n <span v-if=\"part.type === 'element'\" class=\"mint-chem-formula__element\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'subscript'\" class=\"mint-chem-formula__subscript\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'superscript'\" class=\"mint-chem-formula__superscript\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'paren'\" class=\"mint-chem-formula__paren\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'dot'\" class=\"mint-chem-formula__dot\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'charge'\" class=\"mint-chem-formula__charge\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'coefficient'\" class=\"mint-chem-formula__element mint-chem-formula__coefficient\">{{ part.text }}</span>\n </template>\n </span>\n <AdductText v-if=\"adduct\" :value=\"adduct\" size=\"inherit\" />\n <slot />\n </template>\n <span v-else>{{ emptyText }}</span>\n </component>\n</template>\n","<script setup lang=\"ts\">\n/** Renders a chemical formula and optional MS adduct with typographically correct subscripts and charges. */\nimport { computed } from 'vue'\nimport { renderFormulaParts, type FormulaPart } from '../utils/formula'\nimport AdductText from './AdductText.vue'\n\ninterface Props {\n formula?: string | null\n inline?: boolean\n adduct?: string | null\n emptyText?: string\n pill?: boolean\n size?: 'xs' | 'sm' | 'inherit'\n tone?: 'primary' | 'secondary' | 'muted'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n formula: null,\n inline: true,\n adduct: null,\n emptyText: '-',\n pill: false,\n size: 'inherit',\n tone: 'primary',\n})\n\nconst formula = computed(() => props.formula?.trim() ?? '')\nconst title = computed(() => [formula.value, props.adduct?.trim()].filter(Boolean).join(' '))\n\nfunction getParts(f: string): FormulaPart[] {\n return renderFormulaParts(f)\n}\n</script>\n\n<template>\n <component\n :is=\"inline ? 'span' : 'div'\"\n :class=\"[\n 'mint-chem-formula',\n !inline ? 'mint-chem-formula--block' : '',\n `mint-chem-formula--${size}`,\n `mint-chem-formula--${tone}`,\n { 'mint-chem-formula--pill': pill, 'mint-chem-formula--empty': !formula },\n ]\"\n :title=\"title || undefined\"\n >\n <template v-if=\"formula\">\n <span class=\"mint-chem-formula__formula\">\n <template v-for=\"(part, i) in getParts(formula)\" :key=\"i\">\n <span v-if=\"part.type === 'element'\" class=\"mint-chem-formula__element\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'subscript'\" class=\"mint-chem-formula__subscript\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'superscript'\" class=\"mint-chem-formula__superscript\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'paren'\" class=\"mint-chem-formula__paren\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'dot'\" class=\"mint-chem-formula__dot\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'charge'\" class=\"mint-chem-formula__charge\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'coefficient'\" class=\"mint-chem-formula__element mint-chem-formula__coefficient\">{{ part.text }}</span>\n </template>\n </span>\n <AdductText v-if=\"adduct\" :value=\"adduct\" size=\"inherit\" />\n <slot />\n </template>\n <span v-else>{{ emptyText }}</span>\n </component>\n</template>\n","// Private SequenceProgressBar helpers; not part of the public SDK surface since MINT 1.4.\nimport type { SequenceProgress } from './types'\n\nexport function sequenceProgressPercent(progress: SequenceProgress | null | undefined): number {\n if (!progress || progress.total_samples <= 0) return 0\n return Math.round(clamp((progress.current_sample / progress.total_samples) * 100, 0, 100))\n}\n\nexport function sequenceSamplesRemaining(progress: SequenceProgress | null | undefined): number {\n if (!progress) return 0\n return Math.max(0, progress.total_samples - progress.current_sample)\n}\n\nexport function estimateSequenceRemainingSeconds(\n progress: SequenceProgress | null | undefined,\n): number | null {\n if (!progress) return null\n const samplesLeft = sequenceSamplesRemaining(progress)\n if (samplesLeft <= 0) return 0\n\n const explicit = progress.estimated_remaining_seconds\n if (typeof explicit === 'number' && explicit > 0) return explicit\n\n const avg = progress.avg_sample_seconds ?? average(progress.sample_durations)\n if (typeof avg === 'number' && avg > 0) return samplesLeft * avg\n\n const elapsed = progress.elapsed_seconds ?? 0\n if (elapsed > 0 && progress.current_sample > 0) {\n return (elapsed / progress.current_sample) * samplesLeft\n }\n\n return null\n}\n\nexport function estimateSequenceFinishDate(\n progress: SequenceProgress | null | undefined,\n now = new Date(),\n): Date | null {\n if (!progress) return null\n\n const explicit = coerceDate(progress.estimated_finish_time)\n if (explicit) return explicit\n\n const remaining = estimateSequenceRemainingSeconds(progress)\n if (remaining == null || remaining <= 0) return null\n return new Date(now.getTime() + remaining * 1000)\n}\n\nexport function formatSequenceRemaining(progress: SequenceProgress | null | undefined): string | null {\n const remaining = estimateSequenceRemainingSeconds(progress)\n if (remaining == null || remaining <= 0) return null\n\n const minutes = remaining / 60\n if (minutes < 1) return `${Math.round(remaining)}s left`\n if (minutes < 60) return `~${Math.round(minutes)}m left`\n\n const hours = Math.floor(minutes / 60)\n const mins = Math.round(minutes % 60)\n return mins > 0 ? `~${hours}h ${mins}m left` : `~${hours}h left`\n}\n\nexport function formatSequenceEta(\n progress: SequenceProgress | null | undefined,\n locale?: Intl.LocalesArgument,\n options: Intl.DateTimeFormatOptions = {},\n): string | null {\n const finish = estimateSequenceFinishDate(progress)\n if (!finish) return null\n return finish.toLocaleTimeString(locale, {\n hour: '2-digit',\n minute: '2-digit',\n ...options,\n })\n}\n\nfunction average(values: readonly number[] | undefined): number | null {\n if (!values?.length) return null\n const usable = values.filter(value => Number.isFinite(value) && value >= 0)\n if (usable.length === 0) return null\n return usable.reduce((sum, value) => sum + value, 0) / usable.length\n}\n\nfunction coerceDate(value: string | Date | null | undefined): Date | null {\n if (!value) return null\n const date = value instanceof Date ? value : new Date(value)\n return Number.isNaN(date.getTime()) ? null : date\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n","<script setup lang=\"ts\">\n/** Displays LC/MS or GC/MS acquisition sequence progress with ETA and remaining-time labels. */\nimport { computed } from 'vue'\nimport {\n formatSequenceEta,\n formatSequenceRemaining,\n sequenceProgressPercent,\n} from '../instrument'\nimport type { SequenceProgress } from '../types'\nimport ProgressBar from './ProgressBar.vue'\n\ninterface Props {\n progress: SequenceProgress\n compact?: boolean\n label?: string\n showEta?: boolean\n showRemaining?: boolean\n /** Failed injections so far; shown as \"N failed\" in the head when above 0. */\n failed?: number\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n compact: false,\n label: 'samples',\n showEta: true,\n showRemaining: true,\n failed: 0,\n})\n\nconst percent = computed(() => sequenceProgressPercent(props.progress))\nconst remainingLabel = computed(() => formatSequenceRemaining(props.progress))\nconst etaLabel = computed(() => formatSequenceEta(props.progress))\nconst hasFooter = computed(() => {\n if (props.compact) return false\n return (props.showRemaining && remainingLabel.value) || (props.showEta && etaLabel.value)\n})\n</script>\n\n<template>\n <div class=\"mint-sequence-progress\">\n <div class=\"mint-sequence-progress__header\">\n <span class=\"mint-sequence-progress__label\">\n <span class=\"mint-sequence-progress__count\">{{ progress.current_sample }} / {{ progress.total_samples }}</span>\n {{ label }}\n </span>\n <span v-if=\"failed > 0\" class=\"mint-sequence-progress__failed\">{{ failed }} failed</span>\n <span class=\"mint-sequence-progress__percent\">{{ percent }}%</span>\n </div>\n\n <ProgressBar\n :value=\"percent\"\n size=\"sm\"\n aria-label=\"Sequence progress\"\n />\n\n <div v-if=\"hasFooter\" class=\"mint-sequence-progress__footer\">\n <span\n v-if=\"showRemaining && remainingLabel\"\n class=\"mint-sequence-progress__remaining\"\n >\n {{ remainingLabel }}\n </span>\n <span\n v-if=\"showEta && etaLabel\"\n class=\"mint-sequence-progress__eta\"\n >\n ETA {{ etaLabel }}\n </span>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Displays LC/MS or GC/MS acquisition sequence progress with ETA and remaining-time labels. */\nimport { computed } from 'vue'\nimport {\n formatSequenceEta,\n formatSequenceRemaining,\n sequenceProgressPercent,\n} from '../instrument'\nimport type { SequenceProgress } from '../types'\nimport ProgressBar from './ProgressBar.vue'\n\ninterface Props {\n progress: SequenceProgress\n compact?: boolean\n label?: string\n showEta?: boolean\n showRemaining?: boolean\n /** Failed injections so far; shown as \"N failed\" in the head when above 0. */\n failed?: number\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n compact: false,\n label: 'samples',\n showEta: true,\n showRemaining: true,\n failed: 0,\n})\n\nconst percent = computed(() => sequenceProgressPercent(props.progress))\nconst remainingLabel = computed(() => formatSequenceRemaining(props.progress))\nconst etaLabel = computed(() => formatSequenceEta(props.progress))\nconst hasFooter = computed(() => {\n if (props.compact) return false\n return (props.showRemaining && remainingLabel.value) || (props.showEta && etaLabel.value)\n})\n</script>\n\n<template>\n <div class=\"mint-sequence-progress\">\n <div class=\"mint-sequence-progress__header\">\n <span class=\"mint-sequence-progress__label\">\n <span class=\"mint-sequence-progress__count\">{{ progress.current_sample }} / {{ progress.total_samples }}</span>\n {{ label }}\n </span>\n <span v-if=\"failed > 0\" class=\"mint-sequence-progress__failed\">{{ failed }} failed</span>\n <span class=\"mint-sequence-progress__percent\">{{ percent }}%</span>\n </div>\n\n <ProgressBar\n :value=\"percent\"\n size=\"sm\"\n aria-label=\"Sequence progress\"\n />\n\n <div v-if=\"hasFooter\" class=\"mint-sequence-progress__footer\">\n <span\n v-if=\"showRemaining && remainingLabel\"\n class=\"mint-sequence-progress__remaining\"\n >\n {{ remainingLabel }}\n </span>\n <span\n v-if=\"showEta && etaLabel\"\n class=\"mint-sequence-progress__eta\"\n >\n ETA {{ etaLabel }}\n </span>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Status badge for instrument states, plus the platform-derived `never` (no report yet) and `inactive` states. */\nimport { computed } from 'vue'\nimport type { InstrumentBadgeState } from '../types'\n\ninterface Props {\n state: InstrumentBadgeState | string\n label?: string\n pulseWhenRunning?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n pulseWhenRunning: true,\n})\n\nconst stateConfig: Record<InstrumentBadgeState, { label: string; tone: string }> = {\n running: { label: 'Running', tone: 'success' },\n standby: { label: 'Standby', tone: 'warning' },\n idle: { label: 'Idle', tone: 'muted' },\n error: { label: 'Error', tone: 'error' },\n connected: { label: 'Connected', tone: 'success' },\n disconnected: { label: 'Offline', tone: 'muted' },\n never: { label: 'Never reported', tone: 'muted' },\n inactive: { label: 'Inactive', tone: 'muted' },\n}\n\n/** Hollow or non-dot shapes, so states read without relying on color. */\nconst shapeClass: Partial<Record<InstrumentBadgeState, string>> = {\n disconnected: 'mint-instrument-state--offline',\n never: 'mint-instrument-state--never',\n inactive: 'mint-instrument-state--inactive',\n}\n\nconst normalizedState = computed(() => props.state.toLowerCase())\nconst config = computed(() => {\n const match = stateConfig[normalizedState.value as InstrumentBadgeState]\n const raw = normalizedState.value\n return match ?? { label: raw.charAt(0).toUpperCase() + raw.slice(1), tone: 'muted' }\n})\nconst shouldPulse = computed(() => props.pulseWhenRunning && normalizedState.value === 'running')\n</script>\n\n<template>\n <span\n class=\"mint-instrument-state\"\n :class=\"[\n `mint-instrument-state--${config.tone}`,\n { 'mint-instrument-state--pulse': shouldPulse },\n shapeClass[normalizedState as InstrumentBadgeState],\n ]\"\n >\n <span class=\"mint-instrument-state__dot\" aria-hidden=\"true\" />\n {{ label ?? config.label }}\n </span>\n</template>\n","<script setup lang=\"ts\">\n/** Status badge for instrument states, plus the platform-derived `never` (no report yet) and `inactive` states. */\nimport { computed } from 'vue'\nimport type { InstrumentBadgeState } from '../types'\n\ninterface Props {\n state: InstrumentBadgeState | string\n label?: string\n pulseWhenRunning?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n label: undefined,\n pulseWhenRunning: true,\n})\n\nconst stateConfig: Record<InstrumentBadgeState, { label: string; tone: string }> = {\n running: { label: 'Running', tone: 'success' },\n standby: { label: 'Standby', tone: 'warning' },\n idle: { label: 'Idle', tone: 'muted' },\n error: { label: 'Error', tone: 'error' },\n connected: { label: 'Connected', tone: 'success' },\n disconnected: { label: 'Offline', tone: 'muted' },\n never: { label: 'Never reported', tone: 'muted' },\n inactive: { label: 'Inactive', tone: 'muted' },\n}\n\n/** Hollow or non-dot shapes, so states read without relying on color. */\nconst shapeClass: Partial<Record<InstrumentBadgeState, string>> = {\n disconnected: 'mint-instrument-state--offline',\n never: 'mint-instrument-state--never',\n inactive: 'mint-instrument-state--inactive',\n}\n\nconst normalizedState = computed(() => props.state.toLowerCase())\nconst config = computed(() => {\n const match = stateConfig[normalizedState.value as InstrumentBadgeState]\n const raw = normalizedState.value\n return match ?? { label: raw.charAt(0).toUpperCase() + raw.slice(1), tone: 'muted' }\n})\nconst shouldPulse = computed(() => props.pulseWhenRunning && normalizedState.value === 'running')\n</script>\n\n<template>\n <span\n class=\"mint-instrument-state\"\n :class=\"[\n `mint-instrument-state--${config.tone}`,\n { 'mint-instrument-state--pulse': shouldPulse },\n shapeClass[normalizedState as InstrumentBadgeState],\n ]\"\n >\n <span class=\"mint-instrument-state__dot\" aria-hidden=\"true\" />\n {{ label ?? config.label }}\n </span>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Renders the typed panels of an instrument's live status in one card, one tab per panel.\n *\n * `kv` panels render as a key/value list, `table`, `text` and `json` through\n * `GeneratedResultRenderer`, and `bar` as a Plotly bar chart that shows one series at a\n * time. The selected tab and series are kept by title and name across live updates; tabs\n * are keyed by position, so panels that share a title stay separate.\n *\n * @example\n * ```vue\n * <InstrumentStatusPanels :panels=\"status.panels\" meta=\"from MS Planner · 12 s ago\" />\n * ```\n */\nimport { computed, ref } from 'vue'\nimport type { StatusPanel } from '../types'\nimport type { GeneratedAnalysisResult } from '../types/generated-ui'\nimport BaseTabs from './BaseTabs.vue'\nimport GeneratedResultRenderer from './GeneratedResultRenderer.vue'\nimport PlotlyChart from './PlotlyChart.vue'\nimport SectionCard from './SectionCard.vue'\nimport SegmentedControl from './SegmentedControl.vue'\n\ninterface Props {\n panels: StatusPanel[]\n title?: string\n /** Monospace meta next to the title, e.g. the reporting plugin and report age. */\n meta?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Live data',\n meta: undefined,\n})\n\n/** The selected panel's title and, among panels with that title, its occurrence. */\nconst selected = ref<{ title: string, occurrence: number } | null>(null)\nconst selectedSeries = ref('')\n\nconst tabs = computed(() => props.panels.map((item, index) => ({ value: index, label: item.title })))\nconst panelIndex = computed(() => {\n const pick = selected.value\n const matching = props.panels.flatMap((item, index) => (item.title === pick?.title ? [index] : []))\n return matching[pick?.occurrence ?? 0] ?? 0\n})\nconst panel = computed(() => props.panels[panelIndex.value])\n\nfunction selectTab(index: number) {\n const title = props.panels[index]?.title ?? ''\n selected.value = { title, occurrence: props.panels.slice(0, index).filter(item => item.title === title).length }\n}\n\nconst result = computed<GeneratedAnalysisResult | null>(() => {\n const current = panel.value\n if (current?.kind === 'table') return { kind: 'table', columns: current.columns, rows: current.rows }\n if (current?.kind === 'text') return { kind: 'text', text: current.text }\n if (current?.kind === 'json') return { kind: 'json', value: current.value }\n return null\n})\n\nconst bar = computed(() => (panel.value?.kind === 'bar' ? panel.value : null))\nconst series = computed(() => bar.value?.series.find(item => item.name === selectedSeries.value) ?? bar.value?.series[0])\nconst traces = computed(() => (bar.value && series.value\n ? [{ type: 'bar' as const, x: bar.value.categories, y: series.value.values, name: series.value.name }]\n : []))\nconst layout = computed(() => ({\n showlegend: false,\n colorway: ['var(--color-primary)'],\n margin: { t: 8, r: 8, b: 32, l: 56 },\n yaxis: { title: { text: [series.value?.name, series.value?.unit && `(${series.value.unit})`].filter(Boolean).join(' ') } },\n}))\n\nfunction formatValue(value: string | number | boolean | null): string {\n return value === null ? '—' : String(value)\n}\n</script>\n\n<template>\n <SectionCard :title=\"title\" :count=\"meta\" divider=\"light\" class=\"mint-status-panels\">\n <BaseTabs\n :model-value=\"panelIndex\" :options=\"tabs\" class=\"mint-status-panels__tabs\"\n @update:model-value=\"selectTab(Number($event))\"\n />\n\n <dl v-if=\"panel?.kind === 'kv'\" class=\"mint-status-panels__kv\">\n <div v-for=\"([key, value], index) in panel.items\" :key=\"index\" class=\"mint-status-panels__kv-row\">\n <dt class=\"mint-status-panels__kv-key\">{{ key }}</dt>\n <dd class=\"mint-status-panels__kv-value\">{{ formatValue(value) }}</dd>\n </div>\n </dl>\n\n <div v-else-if=\"result\" class=\"mint-status-panels__body\">\n <GeneratedResultRenderer :result=\"result\" :searchable=\"false\" />\n </div>\n\n <div v-else-if=\"bar\" class=\"mint-status-panels__body mint-status-panels__bar\">\n <SegmentedControl\n v-if=\"bar.series.length > 1\" class=\"mint-status-panels__series\"\n :model-value=\"series?.name ?? ''\" :options=\"bar.series.map(item => item.name)\" size=\"sm\"\n @update:model-value=\"selectedSeries = String($event)\"\n />\n <PlotlyChart :data=\"traces\" :layout=\"layout\" variant=\"frame\" :height=\"240\" :header-legend=\"false\" :aria-label=\"bar.title\" />\n </div>\n </SectionCard>\n</template>\n\n<style scoped>\n.mint-status-panels__tabs { padding: 0 var(--mint-section-card-inset); }\n.mint-status-panels__body { padding: 0.75rem var(--mint-section-card-inset); }\n.mint-status-panels__bar { display: flex; flex-direction: column; gap: 0.5rem; }\n.mint-status-panels__series { align-self: flex-end; }\n.mint-status-panels__kv { margin: 0; padding: 0.25rem var(--mint-section-card-inset) 0.5rem; }\n.mint-status-panels__kv-row {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 0.4375rem 0;\n border-bottom: 1px solid var(--border-light);\n}\n.mint-status-panels__kv-row:last-child { border-bottom: 0; }\n.mint-status-panels__kv-key { font-size: 0.75rem; color: var(--text-secondary); }\n.mint-status-panels__kv-value {\n margin: 0;\n min-width: 0;\n font-family: var(--font-mono);\n font-variant-numeric: tabular-nums;\n font-size: 0.75rem;\n color: var(--text-primary);\n text-align: right;\n overflow-wrap: anywhere;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * Renders the typed panels of an instrument's live status in one card, one tab per panel.\n *\n * `kv` panels render as a key/value list, `table`, `text` and `json` through\n * `GeneratedResultRenderer`, and `bar` as a Plotly bar chart that shows one series at a\n * time. The selected tab and series are kept by title and name across live updates; tabs\n * are keyed by position, so panels that share a title stay separate.\n *\n * @example\n * ```vue\n * <InstrumentStatusPanels :panels=\"status.panels\" meta=\"from MS Planner · 12 s ago\" />\n * ```\n */\nimport { computed, ref } from 'vue'\nimport type { StatusPanel } from '../types'\nimport type { GeneratedAnalysisResult } from '../types/generated-ui'\nimport BaseTabs from './BaseTabs.vue'\nimport GeneratedResultRenderer from './GeneratedResultRenderer.vue'\nimport PlotlyChart from './PlotlyChart.vue'\nimport SectionCard from './SectionCard.vue'\nimport SegmentedControl from './SegmentedControl.vue'\n\ninterface Props {\n panels: StatusPanel[]\n title?: string\n /** Monospace meta next to the title, e.g. the reporting plugin and report age. */\n meta?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Live data',\n meta: undefined,\n})\n\n/** The selected panel's title and, among panels with that title, its occurrence. */\nconst selected = ref<{ title: string, occurrence: number } | null>(null)\nconst selectedSeries = ref('')\n\nconst tabs = computed(() => props.panels.map((item, index) => ({ value: index, label: item.title })))\nconst panelIndex = computed(() => {\n const pick = selected.value\n const matching = props.panels.flatMap((item, index) => (item.title === pick?.title ? [index] : []))\n return matching[pick?.occurrence ?? 0] ?? 0\n})\nconst panel = computed(() => props.panels[panelIndex.value])\n\nfunction selectTab(index: number) {\n const title = props.panels[index]?.title ?? ''\n selected.value = { title, occurrence: props.panels.slice(0, index).filter(item => item.title === title).length }\n}\n\nconst result = computed<GeneratedAnalysisResult | null>(() => {\n const current = panel.value\n if (current?.kind === 'table') return { kind: 'table', columns: current.columns, rows: current.rows }\n if (current?.kind === 'text') return { kind: 'text', text: current.text }\n if (current?.kind === 'json') return { kind: 'json', value: current.value }\n return null\n})\n\nconst bar = computed(() => (panel.value?.kind === 'bar' ? panel.value : null))\nconst series = computed(() => bar.value?.series.find(item => item.name === selectedSeries.value) ?? bar.value?.series[0])\nconst traces = computed(() => (bar.value && series.value\n ? [{ type: 'bar' as const, x: bar.value.categories, y: series.value.values, name: series.value.name }]\n : []))\nconst layout = computed(() => ({\n showlegend: false,\n colorway: ['var(--color-primary)'],\n margin: { t: 8, r: 8, b: 32, l: 56 },\n yaxis: { title: { text: [series.value?.name, series.value?.unit && `(${series.value.unit})`].filter(Boolean).join(' ') } },\n}))\n\nfunction formatValue(value: string | number | boolean | null): string {\n return value === null ? '—' : String(value)\n}\n</script>\n\n<template>\n <SectionCard :title=\"title\" :count=\"meta\" divider=\"light\" class=\"mint-status-panels\">\n <BaseTabs\n :model-value=\"panelIndex\" :options=\"tabs\" class=\"mint-status-panels__tabs\"\n @update:model-value=\"selectTab(Number($event))\"\n />\n\n <dl v-if=\"panel?.kind === 'kv'\" class=\"mint-status-panels__kv\">\n <div v-for=\"([key, value], index) in panel.items\" :key=\"index\" class=\"mint-status-panels__kv-row\">\n <dt class=\"mint-status-panels__kv-key\">{{ key }}</dt>\n <dd class=\"mint-status-panels__kv-value\">{{ formatValue(value) }}</dd>\n </div>\n </dl>\n\n <div v-else-if=\"result\" class=\"mint-status-panels__body\">\n <GeneratedResultRenderer :result=\"result\" :searchable=\"false\" />\n </div>\n\n <div v-else-if=\"bar\" class=\"mint-status-panels__body mint-status-panels__bar\">\n <SegmentedControl\n v-if=\"bar.series.length > 1\" class=\"mint-status-panels__series\"\n :model-value=\"series?.name ?? ''\" :options=\"bar.series.map(item => item.name)\" size=\"sm\"\n @update:model-value=\"selectedSeries = String($event)\"\n />\n <PlotlyChart :data=\"traces\" :layout=\"layout\" variant=\"frame\" :height=\"240\" :header-legend=\"false\" :aria-label=\"bar.title\" />\n </div>\n </SectionCard>\n</template>\n\n<style scoped>\n.mint-status-panels__tabs { padding: 0 var(--mint-section-card-inset); }\n.mint-status-panels__body { padding: 0.75rem var(--mint-section-card-inset); }\n.mint-status-panels__bar { display: flex; flex-direction: column; gap: 0.5rem; }\n.mint-status-panels__series { align-self: flex-end; }\n.mint-status-panels__kv { margin: 0; padding: 0.25rem var(--mint-section-card-inset) 0.5rem; }\n.mint-status-panels__kv-row {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 0.4375rem 0;\n border-bottom: 1px solid var(--border-light);\n}\n.mint-status-panels__kv-row:last-child { border-bottom: 0; }\n.mint-status-panels__kv-key { font-size: 0.75rem; color: var(--text-secondary); }\n.mint-status-panels__kv-value {\n margin: 0;\n min-width: 0;\n font-family: var(--font-mono);\n font-variant-numeric: tabular-nums;\n font-size: 0.75rem;\n color: var(--text-primary);\n text-align: right;\n overflow-wrap: anywhere;\n}\n</style>\n","<script setup lang=\"ts\">\n/** Paired start/end time pickers that validate range order (end must follow start) and display computed duration. */\nimport { computed, watch } from 'vue'\nimport type { TimeRange, TimeSlotDisabledFn } from '../types/components'\nimport DatePicker from './DatePicker.vue'\nimport { durationMinutes, compareTime } from '../utils/time'\n\ninterface Props {\n modelValue?: TimeRange\n disabled?: boolean\n invalid?: boolean\n size?: 'sm' | 'md' | 'lg'\n min?: string\n max?: string\n step?: number\n format?: '12h' | '24h'\n showDuration?: boolean\n blockedRanges?: TimeRange[]\n /** Marks slots unavailable in both pickers; a returned string is shown inline as the reason. */\n disabledSlots?: TimeSlotDisabledFn\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n invalid: false,\n size: 'md',\n step: 15,\n format: '24h',\n showDuration: true,\n blockedRanges: () => [],\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: TimeRange | undefined]\n /** Emitted on mount and whenever the order check flips; `false` while end ≤ start. */\n 'validity-change': [valid: boolean]\n}>()\n\nconst startValue = computed(() => props.modelValue?.start)\nconst endValue = computed(() => props.modelValue?.end)\n\nconst duration = computed(() => {\n if (!props.modelValue?.start || !props.modelValue?.end) return null\n return durationMinutes(props.modelValue.start, props.modelValue.end)\n})\n\nconst isInvalid = computed(() => {\n if (!props.modelValue?.start || !props.modelValue?.end) return false\n return compareTime(props.modelValue.start, props.modelValue.end) >= 0\n})\n\n// Always shown; an incomplete or inverted range reads \"—\", never a negative span.\nconst durationLabel = computed(() => {\n if (duration.value === null || isInvalid.value) return '—'\n const hours = Math.floor(duration.value / 60)\n const minutes = duration.value % 60\n return [hours && `${hours} h`, minutes && `${minutes} min`].filter(Boolean).join(' ')\n})\n\nconst isValid = computed(() => !isInvalid.value)\n\nwatch(isValid, (valid) => emit('validity-change', valid), { immediate: true })\n\ndefineExpose({ isValid })\n\nconst endMin = computed(() => {\n if (props.modelValue?.start) return props.modelValue.start\n return props.min\n})\n\nfunction onStartChange(value: string | undefined) {\n if (!value) {\n emit('update:modelValue', undefined)\n return\n }\n const end = props.modelValue?.end || ''\n emit('update:modelValue', { start: value, end })\n}\n\nfunction onEndChange(value: string | undefined) {\n if (!value) {\n emit('update:modelValue', undefined)\n return\n }\n const start = props.modelValue?.start || ''\n emit('update:modelValue', { start, end: value })\n}\n</script>\n\n<template>\n <div class=\"mint-time-range\">\n <div class=\"mint-time-range__start\">\n <DatePicker\n type=\"time\"\n :model-value=\"startValue\"\n :disabled=\"disabled\"\n :invalid=\"invalid || isInvalid\"\n :size=\"size\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :format=\"format\"\n :disabled-slots=\"disabledSlots\"\n placeholder=\"Start\"\n @update:model-value=\"onStartChange\"\n />\n </div>\n\n <span class=\"mint-time-range__separator\" aria-hidden=\"true\">–</span>\n\n <div class=\"mint-time-range__end\">\n <DatePicker\n type=\"time\"\n :model-value=\"endValue\"\n :disabled=\"disabled\"\n :invalid=\"invalid || isInvalid\"\n :size=\"size\"\n :min=\"endMin\"\n :max=\"max\"\n :step=\"step\"\n :format=\"format\"\n :disabled-slots=\"disabledSlots\"\n placeholder=\"End\"\n @update:model-value=\"onEndChange\"\n />\n </div>\n\n <span\n v-if=\"showDuration\"\n :class=\"['mint-time-range__duration', isInvalid ? 'mint-time-range__duration--error' : '']\"\n >\n {{ durationLabel }}\n </span>\n\n <p v-if=\"isInvalid\" class=\"mint-time-range__error\" role=\"alert\">\n End time must be after start time\n </p>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Paired start/end time pickers that validate range order (end must follow start) and display computed duration. */\nimport { computed, watch } from 'vue'\nimport type { TimeRange, TimeSlotDisabledFn } from '../types/components'\nimport DatePicker from './DatePicker.vue'\nimport { durationMinutes, compareTime } from '../utils/time'\n\ninterface Props {\n modelValue?: TimeRange\n disabled?: boolean\n invalid?: boolean\n size?: 'sm' | 'md' | 'lg'\n min?: string\n max?: string\n step?: number\n format?: '12h' | '24h'\n showDuration?: boolean\n blockedRanges?: TimeRange[]\n /** Marks slots unavailable in both pickers; a returned string is shown inline as the reason. */\n disabledSlots?: TimeSlotDisabledFn\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n invalid: false,\n size: 'md',\n step: 15,\n format: '24h',\n showDuration: true,\n blockedRanges: () => [],\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: TimeRange | undefined]\n /** Emitted on mount and whenever the order check flips; `false` while end ≤ start. */\n 'validity-change': [valid: boolean]\n}>()\n\nconst startValue = computed(() => props.modelValue?.start)\nconst endValue = computed(() => props.modelValue?.end)\n\nconst duration = computed(() => {\n if (!props.modelValue?.start || !props.modelValue?.end) return null\n return durationMinutes(props.modelValue.start, props.modelValue.end)\n})\n\nconst isInvalid = computed(() => {\n if (!props.modelValue?.start || !props.modelValue?.end) return false\n return compareTime(props.modelValue.start, props.modelValue.end) >= 0\n})\n\n// Always shown; an incomplete or inverted range reads \"—\", never a negative span.\nconst durationLabel = computed(() => {\n if (duration.value === null || isInvalid.value) return '—'\n const hours = Math.floor(duration.value / 60)\n const minutes = duration.value % 60\n return [hours && `${hours} h`, minutes && `${minutes} min`].filter(Boolean).join(' ')\n})\n\nconst isValid = computed(() => !isInvalid.value)\n\nwatch(isValid, (valid) => emit('validity-change', valid), { immediate: true })\n\ndefineExpose({ isValid })\n\nconst endMin = computed(() => {\n if (props.modelValue?.start) return props.modelValue.start\n return props.min\n})\n\nfunction onStartChange(value: string | undefined) {\n if (!value) {\n emit('update:modelValue', undefined)\n return\n }\n const end = props.modelValue?.end || ''\n emit('update:modelValue', { start: value, end })\n}\n\nfunction onEndChange(value: string | undefined) {\n if (!value) {\n emit('update:modelValue', undefined)\n return\n }\n const start = props.modelValue?.start || ''\n emit('update:modelValue', { start, end: value })\n}\n</script>\n\n<template>\n <div class=\"mint-time-range\">\n <div class=\"mint-time-range__start\">\n <DatePicker\n type=\"time\"\n :model-value=\"startValue\"\n :disabled=\"disabled\"\n :invalid=\"invalid || isInvalid\"\n :size=\"size\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :format=\"format\"\n :disabled-slots=\"disabledSlots\"\n placeholder=\"Start\"\n @update:model-value=\"onStartChange\"\n />\n </div>\n\n <span class=\"mint-time-range__separator\" aria-hidden=\"true\">–</span>\n\n <div class=\"mint-time-range__end\">\n <DatePicker\n type=\"time\"\n :model-value=\"endValue\"\n :disabled=\"disabled\"\n :invalid=\"invalid || isInvalid\"\n :size=\"size\"\n :min=\"endMin\"\n :max=\"max\"\n :step=\"step\"\n :format=\"format\"\n :disabled-slots=\"disabledSlots\"\n placeholder=\"End\"\n @update:model-value=\"onEndChange\"\n />\n </div>\n\n <span\n v-if=\"showDuration\"\n :class=\"['mint-time-range__duration', isInvalid ? 'mint-time-range__duration--error' : '']\"\n >\n {{ durationLabel }}\n </span>\n\n <p v-if=\"isInvalid\" class=\"mint-time-range__error\" role=\"alert\">\n End time must be after start time\n </p>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Card displaying a lab resource with availability status, specs, tags, and a book action. */\nimport { computed } from 'vue'\nimport BasePill from './BasePill.vue'\nimport type { ResourceStatus, ResourceSpec } from '../types/components'\n\ninterface Props {\n name: string\n description?: string\n status?: ResourceStatus\n image?: string\n location?: string\n specs?: ResourceSpec[]\n tags?: string[]\n nextAvailable?: string\n showBookAction?: boolean\n compact?: boolean\n size?: 'sm' | 'md' | 'lg'\n statusLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n status: 'available',\n specs: () => [],\n tags: () => [],\n showBookAction: true,\n compact: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n book: []\n click: []\n}>()\n\nconst defaultStatusLabels: Record<ResourceStatus, string> = {\n 'available': 'Available',\n 'in-use': 'In Use',\n 'maintenance': 'Maintenance',\n 'offline': 'Offline',\n}\n\nconst displayStatusLabel = computed(() => props.statusLabel ?? defaultStatusLabels[props.status])\n\nconst cardClasses = computed(() => [\n 'mint-resource-card',\n `mint-resource-card--${props.size}`,\n { 'mint-resource-card--compact': props.compact },\n])\n\nfunction handleBook(event: MouseEvent) {\n event.stopPropagation()\n emit('book')\n}\n\nfunction handleClick() {\n emit('click')\n}\n</script>\n\n<template>\n <!-- Compact mode -->\n <div v-if=\"compact\" :class=\"cardClasses\" @click=\"handleClick\">\n <slot name=\"status\">\n <span :class=\"['mint-resource-card__status-dot', `mint-resource-card__status-dot--${status}`]\" />\n </slot>\n\n <span class=\"mint-resource-card__name\">{{ name }}</span>\n\n <span :class=\"['mint-resource-card__status-label', `mint-resource-card__status-label--${status}`]\">\n {{ displayStatusLabel }}\n </span>\n\n <slot name=\"action\">\n <button\n v-if=\"showBookAction\"\n type=\"button\"\n class=\"mint-resource-card__book-btn\"\n @click=\"handleBook\"\n >\n Book\n </button>\n </slot>\n </div>\n\n <!-- Card mode -->\n <div v-else :class=\"cardClasses\" @click=\"handleClick\">\n <div class=\"mint-resource-card__content\">\n <div class=\"mint-resource-card__header\">\n <!-- Image / fallback -->\n <div class=\"mint-resource-card__image\">\n <slot name=\"image\">\n <img v-if=\"image\" :src=\"image\" :alt=\"name\" />\n <div v-else class=\"mint-resource-card__image-fallback\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" d=\"M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714a2.25 2.25 0 00.659 1.591L19 14.5M14.25 3.104c.251.023.501.05.75.082M19 14.5l-2.47 2.47a2.25 2.25 0 00-.659 1.59V21a.75.75 0 01-.75.75h-6.24a.75.75 0 01-.75-.75v-2.44a2.25 2.25 0 00-.659-1.59L5 14.5m14 0h-2.25m-10.5 0H4\" />\n </svg>\n </div>\n </slot>\n </div>\n\n <!-- Body -->\n <div class=\"mint-resource-card__body\">\n <div class=\"mint-resource-card__title-row\">\n <h3 class=\"mint-resource-card__name\">{{ name }}</h3>\n\n <slot name=\"status\">\n <div class=\"mint-resource-card__status\">\n <span :class=\"['mint-resource-card__status-dot', `mint-resource-card__status-dot--${status}`]\" />\n <span class=\"mint-resource-card__status-label\">{{ displayStatusLabel }}</span>\n </div>\n </slot>\n </div>\n\n <p v-if=\"description\" class=\"mint-resource-card__description\">{{ description }}</p>\n\n <p v-if=\"location\" class=\"mint-resource-card__location\">Location: {{ location }}</p>\n\n <p v-if=\"nextAvailable\" class=\"mint-resource-card__next-available\">Next available: {{ nextAvailable }}</p>\n </div>\n </div>\n\n <!-- Specs -->\n <div v-if=\"specs.length > 0 || $slots.specs\" class=\"mint-resource-card__specs\">\n <slot name=\"specs\">\n <div v-for=\"spec in specs\" :key=\"spec.label\" class=\"mint-resource-card__spec\">\n <span class=\"mint-resource-card__spec-label\">{{ spec.label }}</span>\n <span class=\"mint-resource-card__spec-value\">{{ spec.value }}</span>\n </div>\n </slot>\n </div>\n\n <!-- Tags + action -->\n <div v-if=\"tags.length > 0 || showBookAction || $slots.action\" class=\"mint-resource-card__footer\">\n <div class=\"mint-resource-card__tags\">\n <BasePill v-for=\"tag in tags\" :key=\"tag\" size=\"sm\">{{ tag }}</BasePill>\n </div>\n\n <div class=\"mint-resource-card__action\">\n <slot name=\"action\">\n <button\n v-if=\"showBookAction\"\n type=\"button\"\n class=\"mint-resource-card__book-btn\"\n @click=\"handleBook\"\n >\n Book Now\n <svg width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </slot>\n </div>\n </div>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Card displaying a lab resource with availability status, specs, tags, and a book action. */\nimport { computed } from 'vue'\nimport BasePill from './BasePill.vue'\nimport type { ResourceStatus, ResourceSpec } from '../types/components'\n\ninterface Props {\n name: string\n description?: string\n status?: ResourceStatus\n image?: string\n location?: string\n specs?: ResourceSpec[]\n tags?: string[]\n nextAvailable?: string\n showBookAction?: boolean\n compact?: boolean\n size?: 'sm' | 'md' | 'lg'\n statusLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n status: 'available',\n specs: () => [],\n tags: () => [],\n showBookAction: true,\n compact: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n book: []\n click: []\n}>()\n\nconst defaultStatusLabels: Record<ResourceStatus, string> = {\n 'available': 'Available',\n 'in-use': 'In Use',\n 'maintenance': 'Maintenance',\n 'offline': 'Offline',\n}\n\nconst displayStatusLabel = computed(() => props.statusLabel ?? defaultStatusLabels[props.status])\n\nconst cardClasses = computed(() => [\n 'mint-resource-card',\n `mint-resource-card--${props.size}`,\n { 'mint-resource-card--compact': props.compact },\n])\n\nfunction handleBook(event: MouseEvent) {\n event.stopPropagation()\n emit('book')\n}\n\nfunction handleClick() {\n emit('click')\n}\n</script>\n\n<template>\n <!-- Compact mode -->\n <div v-if=\"compact\" :class=\"cardClasses\" @click=\"handleClick\">\n <slot name=\"status\">\n <span :class=\"['mint-resource-card__status-dot', `mint-resource-card__status-dot--${status}`]\" />\n </slot>\n\n <span class=\"mint-resource-card__name\">{{ name }}</span>\n\n <span :class=\"['mint-resource-card__status-label', `mint-resource-card__status-label--${status}`]\">\n {{ displayStatusLabel }}\n </span>\n\n <slot name=\"action\">\n <button\n v-if=\"showBookAction\"\n type=\"button\"\n class=\"mint-resource-card__book-btn\"\n @click=\"handleBook\"\n >\n Book\n </button>\n </slot>\n </div>\n\n <!-- Card mode -->\n <div v-else :class=\"cardClasses\" @click=\"handleClick\">\n <div class=\"mint-resource-card__content\">\n <div class=\"mint-resource-card__header\">\n <!-- Image / fallback -->\n <div class=\"mint-resource-card__image\">\n <slot name=\"image\">\n <img v-if=\"image\" :src=\"image\" :alt=\"name\" />\n <div v-else class=\"mint-resource-card__image-fallback\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"1.5\" d=\"M9.75 3.104v5.714a2.25 2.25 0 01-.659 1.591L5 14.5M9.75 3.104c-.251.023-.501.05-.75.082m.75-.082a24.301 24.301 0 014.5 0m0 0v5.714a2.25 2.25 0 00.659 1.591L19 14.5M14.25 3.104c.251.023.501.05.75.082M19 14.5l-2.47 2.47a2.25 2.25 0 00-.659 1.59V21a.75.75 0 01-.75.75h-6.24a.75.75 0 01-.75-.75v-2.44a2.25 2.25 0 00-.659-1.59L5 14.5m14 0h-2.25m-10.5 0H4\" />\n </svg>\n </div>\n </slot>\n </div>\n\n <!-- Body -->\n <div class=\"mint-resource-card__body\">\n <div class=\"mint-resource-card__title-row\">\n <h3 class=\"mint-resource-card__name\">{{ name }}</h3>\n\n <slot name=\"status\">\n <div class=\"mint-resource-card__status\">\n <span :class=\"['mint-resource-card__status-dot', `mint-resource-card__status-dot--${status}`]\" />\n <span class=\"mint-resource-card__status-label\">{{ displayStatusLabel }}</span>\n </div>\n </slot>\n </div>\n\n <p v-if=\"description\" class=\"mint-resource-card__description\">{{ description }}</p>\n\n <p v-if=\"location\" class=\"mint-resource-card__location\">Location: {{ location }}</p>\n\n <p v-if=\"nextAvailable\" class=\"mint-resource-card__next-available\">Next available: {{ nextAvailable }}</p>\n </div>\n </div>\n\n <!-- Specs -->\n <div v-if=\"specs.length > 0 || $slots.specs\" class=\"mint-resource-card__specs\">\n <slot name=\"specs\">\n <div v-for=\"spec in specs\" :key=\"spec.label\" class=\"mint-resource-card__spec\">\n <span class=\"mint-resource-card__spec-label\">{{ spec.label }}</span>\n <span class=\"mint-resource-card__spec-value\">{{ spec.value }}</span>\n </div>\n </slot>\n </div>\n\n <!-- Tags + action -->\n <div v-if=\"tags.length > 0 || showBookAction || $slots.action\" class=\"mint-resource-card__footer\">\n <div class=\"mint-resource-card__tags\">\n <BasePill v-for=\"tag in tags\" :key=\"tag\" size=\"sm\">{{ tag }}</BasePill>\n </div>\n\n <div class=\"mint-resource-card__action\">\n <slot name=\"action\">\n <button\n v-if=\"showBookAction\"\n type=\"button\"\n class=\"mint-resource-card__book-btn\"\n @click=\"handleBook\"\n >\n Book Now\n <svg width=\"12\" height=\"12\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </slot>\n </div>\n </div>\n </div>\n </div>\n</template>\n","/**\n * Composable for generating dilution series used in plate-based experiments.\n * Provides geometric series generation from presets (2x, 3x, ½ log, 10x)\n * and utility functions for series manipulation.\n */\n\nexport interface LevelEntry {\n value: number\n replicates: number\n}\n\nexport interface DilutionPreset {\n label: string\n factor: number | 'half-log'\n}\n\nexport const DEFAULT_PRESETS: DilutionPreset[] = [\n { label: '2x', factor: 2 },\n { label: '3x', factor: 3 },\n { label: '½ log', factor: 'half-log' },\n { label: '10x', factor: 10 },\n]\n\nexport const DEFAULT_UNITS = ['µM', 'nM', 'mM', 'pM']\n\nfunction resolveFactor(factor: number | 'half-log'): number {\n return factor === 'half-log' ? Math.sqrt(10) : factor\n}\n\nfunction roundToSignificant(value: number, digits: number = 4): number {\n if (value === 0) return 0\n const magnitude = Math.floor(Math.log10(Math.abs(value))) + 1\n const factor = Math.pow(10, digits - magnitude)\n return Math.round(value * factor) / factor\n}\n\nexport function generateDilutionSeries(\n startConcentration: number,\n count: number,\n factor: number | 'half-log',\n defaultReplicates: number = 1,\n): LevelEntry[] {\n if (count < 1 || startConcentration <= 0) return []\n\n const divisor = resolveFactor(factor)\n const levels: LevelEntry[] = []\n\n for (let i = 0; i < count; i++) {\n levels.push({\n value: roundToSignificant(startConcentration / Math.pow(divisor, i)),\n replicates: defaultReplicates,\n })\n }\n\n return levels\n}\n\nexport interface UseReagentSeriesReturn {\n generateSeries: (\n start: number,\n count: number,\n factor: number | 'half-log',\n defaultReplicates?: number,\n ) => LevelEntry[]\n sortLevels: (levels: LevelEntry[], order?: 'asc' | 'desc') => LevelEntry[]\n totalPositions: (levels: LevelEntry[]) => number\n}\n\n/** Generates and sorts geometric dilution series (2x, 3x, ½ log, 10x) for plate-based experiments. */\nexport function useReagentSeries(): UseReagentSeriesReturn {\n function generateSeries(\n start: number,\n count: number,\n factor: number | 'half-log',\n defaultReplicates: number = 1,\n ): LevelEntry[] {\n return generateDilutionSeries(start, count, factor, defaultReplicates)\n }\n\n function sortLevels(levels: LevelEntry[], order: 'asc' | 'desc' = 'desc'): LevelEntry[] {\n return [...levels].sort((a, b) =>\n order === 'desc' ? b.value - a.value : a.value - b.value,\n )\n }\n\n function totalPositions(levels: LevelEntry[]): number {\n return levels.reduce((sum, l) => sum + l.replicates, 0)\n }\n\n return { generateSeries, sortLevels, totalPositions }\n}\n","// Lab, plate, chemistry, scheduling and scientific display components (`@morscherlab/mint-sdk/lab`).\n\n// Biological experiment components\nexport { default as WellPlate } from './components/WellPlate.vue'\nexport { default as PlateEditor } from './components/PlateEditor.vue'\nexport { defineWellPlate, definePlateEditor } from './composables/componentSpecs'\n\n// Sample management components\n/** Interactive sample list with drag-and-drop group assignment, color labeling, and smart-group support. */\nexport { default as SampleSelector } from './components/SampleSelector.vue'\n\n// Lab/Experiment components\nexport { default as MoleculeInput } from './components/MoleculeInput.vue'\nexport { default as ConcentrationInput } from './components/ConcentrationInput.vue'\nexport { default as DoseCalculator } from './components/DoseCalculator.vue'\nexport { default as SampleHierarchyTree } from './components/SampleHierarchyTree.vue'\nexport { default as ProtocolStepEditor } from './components/ProtocolStepEditor.vue'\n\n// Scientific display components\nexport { default as PlotlyChart } from './components/PlotlyChart.vue'\nexport { default as ChartContainer } from './components/ChartContainer.vue'\nexport { default as ScientificNumber } from './components/ScientificNumber.vue'\nexport { default as ChemicalFormula } from './components/ChemicalFormula.vue'\nexport { default as AdductText } from './components/AdductText.vue'\n/** Displays LC/MS or GC/MS acquisition sequence progress with ETA and remaining-time labels. */\nexport { default as SequenceProgressBar } from './components/SequenceProgressBar.vue'\n/** Status badge for instrument states: a tone dot (or dash / hollow shape for offline, never-reported and inactive) plus a label. */\nexport { default as InstrumentStateBadge } from './components/InstrumentStateBadge.vue'\n/** Live-data card for an instrument status: one tab per typed panel (key/value, table, text, JSON, bar chart). */\nexport { default as InstrumentStatusPanels } from './components/InstrumentStatusPanels.vue'\n\n// Scientific input components\nexport { default as FormulaInput } from './components/FormulaInput.vue'\nexport { default as SequenceInput } from './components/SequenceInput.vue'\n\n// Scheduling / booking components\nexport { default as TimeRangeInput } from './components/TimeRangeInput.vue'\nexport { default as ScheduleCalendar } from './components/ScheduleCalendar.vue'\nexport { default as ResourceCard } from './components/ResourceCard.vue'\n\n// Workflow components\nexport { default as StepWizard } from './components/StepWizard.vue'\n\n// Time, dose and chemical formula helpers\nexport * as time from './utils/time'\nexport * as dose from './utils/dose'\nexport * as formula from './utils/formula'\n\n// Dose control recipes\nexport {\n defineDoseCalculatorControlProps,\n defineDoseDesignControlModel,\n defineWellPlateControlProps,\n defineWellPlateDoseComponentBindings,\n defineWellPlateDoseControlProps,\n} from './composables/controlSchemaDoseDesign'\nexport type {\n DoseCalculatorControlPropsOptions,\n DoseDesignControlModelOptions,\n WellPlateControlPropsOptions,\n WellPlateDoseControlPropsOptions,\n} from './composables/controlSchemaTypes'\n\n// Lab composables and helpers\nexport {\n useConcentrationUnits,\n type ConcentrationValue,\n type ConcentrationUnit,\n type MolarityUnit,\n type MassVolumeUnit,\n type PercentageUnit,\n type UnitCategory,\n type UseConcentrationUnitsReturn,\n} from './composables/useConcentrationUnits'\nexport {\n useProtocolTemplates,\n type ParameterDefinition,\n type StepTemplate,\n type ValidationResult,\n type UseProtocolTemplatesReturn,\n} from './composables/useProtocolTemplates'\nexport {\n useRackEditor,\n type UseRackEditorOptions,\n type UseRackEditorReturn,\n type PlateEditorListeners,\n} from './composables/useRackEditor'\nexport {\n useSequenceUtils,\n type SequenceType,\n type SequenceStats,\n type SequenceProblems,\n} from './composables/useSequenceUtils'\nexport {\n useScheduleDrag,\n} from './composables/useScheduleDrag'\nexport {\n useReagentSeries,\n generateDilutionSeries,\n DEFAULT_PRESETS,\n DEFAULT_UNITS,\n type LevelEntry,\n type DilutionPreset,\n type UseReagentSeriesReturn,\n} from './composables/useReagentSeries'\nexport {\n useSampleGroups,\n type DisplaySampleSubGroup,\n type SampleGroupNode,\n type SampleGroupSource,\n type SampleMajorGroup,\n type UseSampleGroupsOptions,\n type UseSampleGroupsReturn,\n} from './composables/useSampleGroups'\nexport {\n hexToHsl,\n hslToHex,\n deriveShade,\n type Hsl,\n} from './utils/color'\nexport {\n sampleTypeMarker,\n type SampleMarkerType,\n type SampleTypeMarker,\n} from './utils/sampleTypeMarker'\nexport {\n mintPlotlyTemplate,\n} from './utils/plotlyTemplate'\n"],"mappings":"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EACN,MAAM;EACN,aAAa;EACb,iBAAiB;EACjB,WAAW;EACX,YAAY;GACV;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;IACL,KAAK;GACP;GACA;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;GACP;GACA;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;IACL,KAAK;GACP;GACA;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;IACL,KAAK;GACP;EACF;CACF;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,aAAa;EACb,iBAAiB;EACjB,WAAW;EACX,YAAY;GACV;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;IACV,SAAS;IACT,SAAS;KACP;MAAE,OAAO;MAAO,OAAO;KAAM;KAC7B;MAAE,OAAO;MAAQ,OAAO;KAAO;KAC/B;MAAE,OAAO;MAAO,OAAO;KAAM;KAC7B;MAAE,OAAO;MAAQ,OAAO;KAAO;KAC/B;MAAE,OAAO;MAAS,OAAO;KAAQ;KACjC;MAAE,OAAO;MAAS,OAAO;KAAQ;IACnC;GACF;GACA;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;GACP;GACA;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;IACL,KAAK;GACP;EACF;CACF;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,aAAa;EACb,iBAAiB;EACjB,WAAW;EACX,YAAY;GACV;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;IACV,aAAa;GACf;GACA;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;GACP;GACA;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;GACZ;EACF;CACF;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,aAAa;EACb,iBAAiB;EACjB,WAAW;EACX,YAAY;GACV;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;IACV,SAAS;KACP;MAAE,OAAO;MAAgB,OAAO;KAAe;KAC/C;MAAE,OAAO;MAAc,OAAO;KAAa;KAC3C;MAAE,OAAO;MAAkB,OAAO;KAAiB;KACnD;MAAE,OAAO;MAAqB,OAAO;KAAoB;KACzD;MAAE,OAAO;MAAS,OAAO;KAAQ;IACnC;GACF;GACA;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;IACV,aAAa;GACf;GACA;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;IACV,aAAa;GACf;EACF;CACF;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,aAAa;EACb,iBAAiB;EACjB,WAAW;EACX,YAAY;GACV;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;IACL,KAAK;GACP;GACA;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;GACP;GACA;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;IACL,KAAK;GACP;EACF;CACF;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,aAAa;EACb,iBAAiB;EACjB,WAAW;EACX,YAAY;GACV;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;IACV,aAAa;GACf;GACA;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;IACV,aAAa;GACf;GACA;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;GACP;EACF;CACF;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,aAAa;EACb,iBAAiB;EACjB,WAAW;EACX,YAAY;GACV;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;IACV,SAAS;IACT,SAAS;KACP;MAAE,OAAO;MAAW,OAAO;KAAiB;KAC5C;MAAE,OAAO;MAAU,OAAO;KAAS;KACnC;MAAE,OAAO;MAAU,OAAO;KAAiB;KAC3C;MAAE,OAAO;MAAa,OAAO;KAAY;IAC3C;GACF;GACA;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;GACP;GACA;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;IACV,aAAa;GACf;EACF;CACF;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,aAAa;EACb,iBAAiB;EACjB,WAAW;EACX,YAAY,CACV;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,UAAU;GACV,aAAa;EACf,GACA;GACE,KAAK;GACL,OAAO;GACP,MAAM;GACN,UAAU;GACV,aAAa;EACf,CACF;CACF;AACF;;;ACtSA,IAAM,cAAc;AAEpB,SAAS,eAAe,OAAuC;CAC7D,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAChD,MAAM,YAAY;CAClB,OACE,OAAO,UAAU,OAAO,YACrB,OAAO,UAAU,SAAS,YAC1B,OAAO,UAAU,SAAS,YAC1B,MAAM,QAAQ,UAAU,UAAU;AAEzC;AAEA,SAAS,sBAAsC;CAC7C,IAAI;EACF,MAAM,SAAS,aAAa,QAAQ,WAAW;EAC/C,IAAI,QAAQ;GACV,MAAM,SAAS,KAAK,MAAM,MAAM;GAChC,OAAO,MAAM,QAAQ,MAAM,IAAI,OAAO,OAAO,cAAc,IAAI,CAAC;EAClE;CACF,QAAQ,CAER;CACA,OAAO,CAAC;AACV;AAEA,SAAS,6BAA6B,WAA2B;CAC/D,IAAI;EACF,aAAa,QAAQ,aAAa,KAAK,UAAU,SAAS,CAAC;CAC7D,QAAQ,CAER;AACF;;AAGA,SAAgB,uBAAmD;CACjE,MAAM,kBAAkB,IAAoB,oBAAoB,CAAC;CAEjE,MAAM,eAAe,eAAe;EAClC,OAAO,CAAC,GAAG,6BAA6B,GAAG,gBAAgB,KAAK;CAClE,CAAC;CAED,SAAS,kBAAkB,MAAkD;EAE3E,OACE,gBAAgB,MAAM,MAAM,MAAM,EAAE,SAAS,IAAI,KACjD,4BAA4B,MAAM,MAAM,EAAE,SAAS,IAAI;CAE3D;CAEA,SAAS,gBAAgB,IAAsC;EAC7D,OACE,gBAAgB,MAAM,MAAM,MAAM,EAAE,OAAO,EAAE,KAC7C,4BAA4B,MAAM,MAAM,EAAE,OAAO,EAAE;CAEvD;CAEA,SAAS,mBAAmB,UAAwB;EAClD,MAAM,cAAc;GAClB,GAAG;GACH,WAAW;GACX,IAAI,SAAS,MAAM,UAAU,KAAK,IAAI;EACxC;EAEA,MAAM,QAAQ,gBAAgB,MAAM,WAAW,MAAM,EAAE,OAAO,YAAY,EAAE;EAC5E,IAAI,SAAS,GACX,gBAAgB,MAAM,SAAS;OAE/B,gBAAgB,MAAM,KAAK,WAAW;EAGxC,6BAA6B,gBAAgB,KAAK;CACpD;CAEA,SAAS,qBAAqB,YAAoB;EAChD,MAAM,QAAQ,gBAAgB,MAAM,WAAW,MAAM,EAAE,OAAO,UAAU;EACxE,IAAI,SAAS,GAAG;GACd,gBAAgB,MAAM,OAAO,OAAO,CAAC;GACrC,6BAA6B,gBAAgB,KAAK;EACpD;CACF;CAEA,SAAS,aAAa,MAAoB,UAA0C;EAClF,MAAM,SAAiC,CAAC;EAExC,IAAI,CAAC,KAAK,QAAQ,KAAK,KAAK,KAAK,MAAM,IACrC,OAAO,OAAO;EAGhB,KAAK,MAAM,SAAS,SAAS,YAAY;GACvC,IAAI,MAAM,UAAU;IAClB,MAAM,QAAQ,KAAK,aAAa,MAAM;IACtC,IAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,IACrD,OAAO,MAAM,OAAO,GAAG,MAAM,MAAM;GAEvC;GAEA,IAAI,MAAM,SAAS,YAAY,MAAM,SAAS,iBAAiB,MAAM,SAAS,YAAY;IACxF,MAAM,QAAQ,KAAK,aAAa,MAAM;IACtC,IAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,IAAI;KACzD,MAAM,WAAW,OAAO,KAAK;KAC7B,IAAI,MAAM,QAAQ,GAChB,OAAO,MAAM,OAAO,GAAG,MAAM,MAAM;UAC9B;MACL,IAAI,MAAM,QAAQ,KAAA,KAAa,WAAW,MAAM,KAC9C,OAAO,MAAM,OAAO,GAAG,MAAM,MAAM,oBAAoB,MAAM;MAE/D,IAAI,MAAM,QAAQ,KAAA,KAAa,WAAW,MAAM,KAC9C,OAAO,MAAM,OAAO,GAAG,MAAM,MAAM,mBAAmB,MAAM;KAEhE;IACF;GACF;EACF;EAEA,OAAO;GACL,OAAO,OAAO,KAAK,MAAM,CAAC,CAAC,WAAW;GACtC;EACF;CACF;CAEA,SAAS,uBACP,UACA,WACc;EAEd,MAAM,aAAsC,CAAC;EAC7C,KAAK,MAAM,SAAS,SAAS,YAC3B,IAAI,MAAM,YAAY,KAAA,GACpB,WAAW,MAAM,OAAO,MAAM;EAIlC,OAAO;GACL,IAAI,QAAQ,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC;GAC/D,MAAM,SAAS;GACf,MAAM,SAAS;GACf,aAAa,SAAS;GACtB,UAAU,SAAS;GACnB,QAAQ;GACR;GACA,OAAO;GACP,GAAG;EACL;CACF;CAEA,SAAS,qBAAqB,OAAgB,OAAoC;EAChF,IAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,IACrD,OAAO;EAGT,IAAI,MAAM,SAAS,YAAY,MAAM,SAAS;GAC5C,MAAM,SAAS,MAAM,QAAQ,MAAM,MAAM,EAAE,UAAU,KAAK;GAC1D,IAAI,QAAQ,OAAO,OAAO;EAC5B;EAEA,IAAI,MAAM,SAAS,mBAAmB,OAAO,UAAU,UAAU;GAC/D,MAAM,OAAO;GACb,OAAO,GAAG,KAAK,MAAM,GAAG,KAAK;EAC/B;EAEA,MAAM,WAAW,OAAO,KAAK;EAC7B,IAAI,MAAM,MACR,OAAO,GAAG,SAAS,GAAG,MAAM;EAG9B,OAAO;CACT;CAEA,OAAO;EACL,kBAAkB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF;;;ACjNA,IAAa,2BAA6D;CACxE,YAAY;CACZ,MAAM;CACN,UAAU;CACV,aAAa;CACb,UAAU;CACV,YAAY;CACZ,KAAK;CACL,QAAQ;AACV;AAEA,SAAgB,uBAAuB,SAAqC;CAC1E,IAAI,YAAY,KAAA,GAAW,OAAO;CAClC,IAAI,UAAU,IAAI,OAAO,GAAG,QAAQ;CACpC,MAAM,QAAQ,KAAK,MAAM,UAAU,EAAE;CACrC,MAAM,OAAO,UAAU;CACvB,OAAO,OAAO,IAAI,GAAG,MAAM,IAAI,KAAK,KAAK,GAAG,MAAM;AACpD;;;;;;;;;;;;;ECFA,MAAM,QAAQ;EAEd,MAAM,OAAO;EAIb,MAAM,YAAY,MAAM;EAExB,MAAM,YAAY,eAAe;GAAC;GAAU;GAAe;EAAU,CAAC,CAAC,SAAS,MAAM,UAAU,IAAI,CAAC;EAErG,MAAM,cAAc,eAClB,MAAM,UACP;EAED,MAAM,eAAe,eACnB,OAAO,MAAM,eAAe,WAAW,MAAM,aAAa,IAC3D;EAED,MAAM,qBAAqB,eACzB,MAAM,UACP;EAED,MAAM,cAAc,eAAe,MAAM,UAAU,gBAC7C,MAAM,UAAU,SAAS,YAAY,uBAAuB,SAAS,MAAM,UAAU,MAAM,YAAY,IAAI;;EAGjH,SAAS,YAAY,OAA+B;GAClD,KAAK,qBAAqB,SAAS,KAAA,CAAS;EAC9C;;GAIE,OAAA,UAAA,GAAA,YAgDY,mBAAA;IA/CT,OAAO,QAAA,UAAU;IACjB,UAAU,QAAA,UAAU;IACpB,OAAO,QAAA;IACP,YAAU,MAAA,SAAA;IACX,QAAO;;IAEP,SAAA,cAWE,CAVM,UAAA,SADR,UAAA,GAAA,YAWE,qBAAA;;KATC,IAAI,MAAA,SAAA;KACL,MAAK;KACJ,eAAa,aAAA;KACb,KAAK,QAAA,UAAU;KACf,KAAK,QAAA,UAAU;KACf,MAAM,QAAA,UAAU;KAChB,aAAa,YAAA;KACb,SAAO,CAAA,CAAI,QAAA;KACX,uBAAoB;;;;;;;;;IAIV,CAAA,KAAA,QAAA,UAAU,SAAI,YAD3B,UAAA,GAAA,YASE,oBAAA;;KAPC,IAAI,MAAA,SAAA;KACL,MAAK;KACJ,eAAa,YAAA;KACb,SAAS,QAAA,UAAU,WAAO,CAAA;KAC1B,aAAW,UAAY,QAAA,UAAU,MAAM,YAAW;KAClD,SAAO,CAAA,CAAI,QAAA;KACX,uBAAkB,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,qBAAsB,MAAM;;;;;;;IAI1C,CAAA,KAAA,QAAA,UAAU,SAAI,mBAD3B,UAAA,GAAA,YAME,4BAAA;;KAJC,eAAa,mBAAA;KACb,SAAO,CAAA,CAAI,QAAA;KACZ,MAAK;KACJ,uBAAkB,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,qBAAsB,MAAM;IAGvD,GAAA,MAAA,GAAA,CAAA,eAAA,SAAA,CAAA,MAAA,UAAA,GAAA,YAQE,mBAAA;;KANC,IAAI,MAAA,SAAA;KACL,MAAK;KACJ,eAAa,YAAA;KACb,aAAa,YAAA;KACb,SAAO,CAAA,CAAI,QAAA;KACX,uBAAoB;;;;;;;;;;;;;;;;;;;AE3E3B,SAAgB,8BACd,UAC0B;CAC1B,MAAM,aAAsC,CAAC;CAC7C,KAAK,MAAM,aAAa,SAAS,YAC/B,IAAI,UAAU,YAAY,KAAA,GACxB,WAAW,UAAU,OAAO,UAAU;CAI1C,OAAO;EACL,MAAM,SAAS;EACf,aAAa,SAAS,eAAe;EACrC,UAAU,SAAS;EACnB;CACF;AACF;AAEA,SAAgB,6BACd,MACA,mBACoC;CAGpC,OAAO;EACL,oBAHe,kBAAkB,KAAK,IAGlB,CAAA,EAAU,MAAM;EACpC,QAAQ;GACN,MAAM,KAAK;GACX,aAAa,KAAK,eAAe;GACjC,UAAU,KAAK;GACf,YAAY,EAAE,GAAI,KAAK,cAAc,CAAC,EAAG;EAC3C;CACF;AACF;AAEA,SAAgB,yBACd,UACA,QACA,YACc;CACd,OAAO;EACL,IAAI,YAAY,MAAM;EACtB,MAAM,SAAS;EACf,MAAM,OAAO,QAAQ,SAAS;EAC9B,aAAa,OAAO;EACpB,UAAU,OAAO,YAAY,SAAS;EACtC,QAAS,YAAY,UAAU;EAC/B,YAAY,EAAE,GAAG,OAAO,WAAW;EACnC,OAAO,YAAY,SAAS;CAC9B;AACF;AAEA,SAAgB,2BACd,UACA,YACA,sBACQ;CACR,OAAO,SAAS,WACb,QAAQ,cACP,WAAW,UAAU,SAAS,KAAA,KAC3B,WAAW,UAAU,SAAS,EAClC,CAAC,CACD,KAAK,cAAc,qBAAqB,WAAW,UAAU,MAAM,SAAS,CAAC,CAAC,CAC9E,KAAK,IAAI;AACd;AAEA,SAAgB,4BACd,UACA,QACA,aAAa,UAAU,KAAK,IAAI,KAClB;CACd,OAAO;EACL,IAAI;EACJ,MAAM,SAAS;EACf,MAAM,OAAO,QAAQ,SAAS;EAC9B,aAAa,OAAO;EACpB,iBAAiB,OAAO;EACxB,YAAY,SAAS,WAAW,KAAK,eAAe;GAClD,GAAG;GACH,SAAS,OAAO,WAAW,UAAU;EACvC,EAAE;EACF,WAAW;CACb;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECjEA,MAAM,QAAQ;EAKd,MAAM,OAAO;EAOb,MAAM,EACJ,cACA,mBACA,cACA,yBACE,qBAAqB;EAGzB,MAAM,UAAU,MAAM;EACtB,MAAM,qBAAqB,IAAmB,IAAI;EAClD,MAAM,WAAW,IAAI,EAAE;EACvB,MAAM,kBAAkB,IAAI,EAAE;EAC9B,MAAM,eAAe,IAAwB;EAC7C,MAAM,aAAa,IAA6B,CAAC,CAAC;EAClD,MAAM,mBAAmB,IAA4B,CAAC,CAAC;EAGvD,MAAM,qBAAqB,eAAe;GACxC,IAAI,MAAM,aAAa,MAAM,UAAU,SAAS,GAC9C,OAAO,MAAM;GAEf,MAAM,WAAW,CAAC,GAAG,aAAa,KAAK;GACvC,IAAI,MAAM,iBACR,SAAS,KAAK,GAAG,MAAM,eAAe;GAExC,OAAO;EACT,CAAC;EAED,MAAM,kBAAkB,eACtB,mBAAmB,MAAM,KAAK,OAAO;GAAE,OAAO,EAAE;GAAI,OAAO,EAAE;GAAM,aAAa,EAAE;EAAY,EAAE,CAClG;EAGA,MAAM,mBAAmB,eAAe;GACtC,IAAI,CAAC,mBAAmB,OAAO,OAAO;GACtC,OAAO,mBAAmB,MAAM,MAAM,MAAM,EAAE,OAAO,mBAAmB,KAAK,KAAK;EACpF,CAAC;EAGD,MAAM,cAAc,eAAoC;GACtD,IAAI,CAAC,iBAAiB,OAAO,OAAO;GACpC,OAAO,yBAAyB,iBAAiB,OAAO,aAAa,GAAG,MAAM,UAAU;EAC1F,CAAC;EAED,MAAM,gBAAgB,eAAe;GACnC,IAAI,CAAC,iBAAiB,SAAS,CAAC,YAAY,OAAO,OAAO;GAC1D,OAAO,2BACL,iBAAiB,OACjB,WAAW,OACX,oBACF;EACF,CAAC;EAED,SAAS,eAAyC;GAChD,OAAO;IACL,MAAM,SAAS;IACf,aAAa,gBAAgB;IAC7B,UAAU,aAAa;IACvB,YAAY,WAAW;GACzB;EACF;EAEA,SAAS,kBAAkB,QAAkC;GAC3D,SAAS,QAAQ,OAAO;GACxB,gBAAgB,QAAQ,OAAO;GAC/B,aAAa,QAAQ,OAAO;GAC5B,WAAW,QAAQ,EAAE,GAAG,OAAO,WAAW;EAC5C;EAEA,SAAS,aAAa,MAAoB;GACxC,MAAM,QAAQ,6BAA6B,MAAM,iBAAiB;GAClE,mBAAmB,QAAQ,MAAM;GACjC,kBAAkB,MAAM,MAAM;EAChC;EAGA,SAAS,eAAe,UAAwB;GAC9C,mBAAmB,QAAQ,SAAS;GAGpC,IAAI,MAAM,SAAS,UACjB,kBAAkB,8BAA8B,QAAQ,CAAC;EAE7D;EAEA,SAAS,qBAAqB,IAAqB;GACjD,MAAM,WAAW,mBAAmB,MAAM,MAAM,MAAM,EAAE,OAAO,EAAE;GACjE,IAAI,UAAU,eAAe,QAAQ;EACvC;EAGA,SAAS,kBAAkB,KAAa,OAAgB;GACtD,WAAW,MAAM,OAAO;GAExB,IAAI,iBAAiB,MAAM,MACzB,OAAO,iBAAiB,MAAM;EAElC;EAGA,SAAS,aAAa;GACpB,IAAI,CAAC,iBAAiB,SAAS,CAAC,YAAY,OAAO;GAEnD,MAAM,SAAS,aAAa,YAAY,OAAO,iBAAiB,KAAK;GACrE,iBAAiB,QAAQ,OAAO;GAEhC,IAAI,OAAO,OAAO;IAChB,KAAK,QAAQ,YAAY,KAAK;IAC9B,KAAK,qBAAqB,YAAY,KAAK;GAC7C;EACF;EAGA,SAAS,qBAAqB;GAC5B,IAAI,CAAC,iBAAiB,OAAO;GAC7B,KAAK,iBAAiB,4BAA4B,iBAAiB,OAAO,aAAa,CAAC,CAAC;EAC3F;EAGA,SAAS,eAAe;GACtB,KAAK,QAAQ;EACf;EAEA,gBAAgB;GACd,IAAI,MAAM,YACR,aAAa,MAAM,UAAU;QACxB,IAAI,mBAAmB,MAAM,SAAS,GAC3C,eAAe,mBAAmB,MAAM,EAAE;EAE9C,CAAC;EAGD,YACQ,MAAM,aACX,SAAS;GACR,IAAI,MACF,aAAa,IAAI;EAErB,CACF;;GAIE,OAAA,UAAA,GAAA,mBAgGM,OAhGN,cAgGM;IA9FJ,mBAKM,OALN,cAKM,CAJJ,mBAEO,QAFP,cAEO,gBADF,QAAA,SAAI,WAAA,gBAAA,WAAA,GAAA,CAAA,GAEG,QAAA,cAAc,QAAA,aAA1B,UAAA,GAAA,mBAAoH,QAApH,cAAoH,gBAAtC,QAAA,UAAU,IAAG,QAAG,gBAAG,QAAA,SAAS,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA;IAI5G,mBA6CM,OA7CN,cA6CM;KA5CJ,YAWY,mBAAA;MAXD,OAAM;MAAY,YAAQ,GAAK,MAAA,OAAA,EAAO;MAAa,QAAO;;MACnE,SAAA,cASE,CATF,YASE,oBAAA;OARA,YAAA;OACA,MAAK;OACJ,mBAAe,GAAK,MAAA,OAAA,EAAO;OAC3B,eAAa,mBAAA,SAAsB,KAAA;OACnC,SAAS,gBAAA;OACV,aAAY;OACZ,sBAAmB;OAClB,uBAAoB;;;;;;;;KAIzB,YAQY,mBAAA;MARD,OAAM;MAAY,UAAA;MAAU,OAAO,iBAAA,MAAiB;MAAO,YAAQ,GAAK,MAAA,OAAA,EAAO;MAAS,QAAO;;MACxG,SAAA,cAME,CANF,YAME,mBAAA;OALC,IAAE,GAAK,MAAA,OAAA,EAAO;OACN,YAAA,SAAA;OAAA,uBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,SAAQ,QAAA;OACjB,MAAK;OACL,aAAY;OACX,SAAO,CAAA,CAAI,iBAAA,MAAiB;;;;;;;;KAKjB,iBAAA,SACd,UAAA,IAAA,GAAA,mBAOE,UAAA,EAAA,KAAA,EAAA,GAAA,WANgB,iBAAA,MAAiB,aAA1B,UAAK;MADd,OAAA,UAAA,GAAA,YAOE,oCAAA;OALC,KAAK,MAAM;OACX,WAAW;OACX,eAAa,WAAA,MAAW,MAAM;OAC9B,OAAO,iBAAA,MAAiB,MAAM;OAC9B,wBAAkB,WAAE,kBAAkB,MAAM,KAAK,MAAM;;;;;;;;KAI5D,YAQY,mBAAA;MARD,OAAM;MAAe,YAAQ,GAAK,MAAA,OAAA,EAAO;MAAgB,QAAO;;MACzE,SAAA,cAME,CANF,YAME,mBAAA;OALA,MAAK;OACJ,IAAE,GAAK,MAAA,OAAA,EAAO;OACN,YAAA,gBAAA;OAAA,uBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,gBAAe,QAAA;OACxB,aAAY;OACX,MAAM;;;;;IAMF,QAAA,eAAe,YAAA,SAA1B,UAAA,GAAA,mBAuBM,OAvBN,cAuBM,CAtBJ,OAAA,OAAA,OAAA,KAAA,mBAA8D,OAAA,EAAzD,OAAM,sCAAqC,GAAC,WAAO,EAAA,IACxD,mBAoBM,OApBN,cAoBM;KAnBJ,mBASM,OATN,cASM,EARJ,UAAA,GAAA,mBAOM,OAPN,cAOM,CANJ,mBAKE,QAAA;MAJA,kBAAe;MACf,mBAAgB;MAChB,gBAAa;MACZ,GAAG,MAAA,wBAAA,CAAa,CAAC,YAAA,MAAY;;KAIpC,mBAKM,OALN,eAKM,CAJJ,mBAA4E,OAA5E,eAA4E,gBAAzB,YAAA,MAAY,IAAI,GAAA,CAAA,GACxD,cAAA,SAAX,UAAA,GAAA,mBAEM,OAFN,eAEM,gBADD,cAAA,KAAa,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA;KAGT,YAAA,MAAY,YAAvB,UAAA,GAAA,mBAEM,OAFN,eAEM,gBADD,MAAA,sBAAA,CAAc,CAAC,YAAA,MAAY,QAAQ,CAAA,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;;IAM5C,mBAWM,OAXN,eAWM;KAVJ,YAEa,oBAAA;MAFD,SAAQ;MAAQ,MAAK;MAAM,SAAO;;MAAoB,SAAA,cAElE,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAFkE,gBAAA,sBAElE,EAAA,CAAA,EAAA,CAAA;;;KACA,OAAA,OAAA,OAAA,KAAA,mBAAoD,OAAA,EAA/C,OAAM,uCAAsC,GAAA,MAAA,EAAA;KACjD,YAEa,oBAAA;MAFD,SAAQ;MAAY,MAAK;MAAM,SAAO;;MAAc,SAAA,cAEhE,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAFgE,gBAAA,YAEhE,EAAA,CAAA,EAAA,CAAA;;;KACA,YAEa,oBAAA;MAFD,SAAQ;MAAU,MAAK;MAAM,UAAQ,CAAG,iBAAA;MAAmB,SAAO;;MAC5E,SAAA,cAAqD,CAAlD,gBAAA,gBAAA,QAAA,SAAI,WAAA,aAAA,cAAA,GAAA,CAAA,CAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EEjRf,MAAM,QAAQ;EAMd,MAAM,SAAS,IAAI,KAAK;EACxB,IAAI;EAEJ,MAAM,cAAkC;GACtC,CAAC,cAAM,GAAG;GACV,CAAC,KAAK,GAAG;GACT,CAAC,KAAK,GAAG;GACT,CAAC,KAAK,GAAG;GACT,CAAC,MAAM,GAAG;GACV,CAAC,MAAM,GAAQ;GACf,CAAC,MAAM,GAAG;GACV,CAAC,OAAO,GAAG;EACb;EAEA,SAAS,iBAAiB,GAAW,QAAwB;GAC3D,OAAO,EAAE,QAAQ,MAAM,CAAC,CAAC,QAAQ,UAAU,EAAE,KAAK;EACpD;EAWA,MAAM,YAAY,eAAgC;GAChD,MAAM,IAAI,MAAM;GAEhB,IAAI,CAAC,OAAO,SAAS,CAAC,GAAG,OAAO;IAAE,MAAM;IAAS,OAAO;GAAG;GAC3D,IAAI,MAAM,GAAG,OAAO;IAAE,MAAM;IAAS,OAAO;GAAI;GAEhD,MAAM,OAAO,KAAK,IAAI,CAAC;GAEvB,IAAI,MAAM,aAAa,WAAW;IAChC,KAAK,MAAM,CAAC,WAAW,WAAW,aAChC,IAAI,QAAQ,YAAY,MACtB,OAAO;KACL,MAAM;KACN,OAAO,iBAAiB,IAAI,WAAW,MAAM,SAAS;KACtD;IACF;IAGJ,OAAO;KAAE,MAAM;KAAS,OAAO,iBAAiB,GAAG,MAAM,SAAS;IAAE;GACtE;GAOA,IAAI,EAJF,MAAM,aAAa,gBACnB,MAAM,aAAa,iBAClB,MAAM,aAAa,WAAW,OAAO,OAAQ,QAAQ,OAGtD,OAAO;IAAE,MAAM;IAAS,OAAO,iBAAiB,GAAG,MAAM,SAAS;GAAE;GAGtE,IAAI,MAAM,KAAK,MAAM,KAAK,MAAM,IAAI,CAAC;GAErC,IAAI,MAAM,aAAa,eACrB,MAAM,KAAK,MAAM,MAAM,CAAC,IAAI;GAM9B,OAAO;IACL,MAAM;IACN,UAJkB,iBADH,IAAI,KAAK,IAAI,IAAI,GAAG,GACU,MAAM,SAIzC;IACV,UAAU;IACV,aAAa,OAAO,GAAG,CAAC,CAAC,QAAQ,KAAK,GAAQ;GAChD;EACF,CAAC;EAED,MAAM,iBAAiB,eAAe;GACpC,IAAI,OAAO,MAAM,MAAM,KAAK,GAAG,OAAO;GACtC,IAAI,MAAM,UAAU,UAAU,OAAO;GACrC,IAAI,MAAM,UAAU,WAAW,OAAO;GACtC,OAAO;EACT,CAAC;EAED,MAAM,iBAAiB,eAAe;GACpC,IAAI,eAAe,OAAO,OAAO,eAAe;GAChD,MAAM,IAAI,UAAU;GACpB,IAAI;GACJ,IAAI,EAAE,SAAS,cACb,OAAO,GAAG,EAAE,SAAS,GAAG,EAAE;QACrB,IAAI,EAAE,SAAS,WACpB,OAAO,GAAG,EAAE,QAAQ,EAAE,UAAU;QAEhC,OAAO,EAAE,SAAS;GAEpB,IAAI,MAAM,MAAM,QAAQ,IAAI,MAAM;GAClC,OAAO;EACT,CAAC;EAED,MAAM,SAAS,IAAiB;;EAGhC,SAAS,gBAAgB,OAAuB;GAC9C,MAAM,YAAY,SAAS,aAAa;GACxC,MAAM,OAAO,OAAO;GACpB,IAAI,CAAC,aAAa,UAAU,eAAe,CAAC,MAAM,SAAS,UAAU,UAAU,KAAK,CAAC,KAAK,SAAS,UAAU,SAAS,GAAG;GACzH,MAAM,eAAe,QAAQ,cAAc,eAAe,KAAK;GAC/D,MAAM,eAAe;EACvB;EAEA,eAAe,kBAAkB;GAC/B,IAAI;IACF,MAAM,UAAU,UAAU,UAAU,eAAe,KAAK;IACxD,OAAO,QAAQ;IACf,aAAa,WAAW;IACxB,cAAc,iBAAiB;KAAE,OAAO,QAAQ;IAAM,GAAG,IAAI;GAC/D,QAAQ,CAER;EACF;;GAIE,OAAA,UAAA,GAAA,mBAgDO,QAAA;IAhDG,SAAA;IAAJ,KAAI;IAAS,OAAM;IAAmB,QAAM;;IAEhC,eAAA,SACd,UAAA,GAAA,mBAAgE,QAAhE,cAAgE,gBAAxB,eAAA,KAAc,GAAA,CAAA,KAInC,UAAA,MAAU,SAAI,gBAAnC,UAAA,GAAA,mBAOW,UAAA,EAAA,KAAA,EAAA,GAAA;KANT,mBAAuE,QAAvE,cAAuE,gBAA5B,UAAA,MAAU,QAAQ,GAAA,CAAA;KAC7D,OAAA,OAAA,OAAA,KAAA,mBAAmD,QAAA,EAA7C,OAAM,yBAAwB,GAAC,KAAO,EAAA;KAC5C,OAAA,OAAA,OAAA,KAAA,mBAA6C,QAAA,EAAvC,OAAM,wBAAuB,GAAC,MAAE,EAAA;KAEtC,mBAAkE,QAAlE,cAAkC,MAAC,gBAAG,UAAA,MAAU,QAAQ,GAAA,CAAA;KACxD,mBAAoG,QAAA;MAA9F,OAAM;MAA6B,iBAAe,UAAA,MAAU;MAAa,eAAY;;IAIxE,GAAA,EAAA,KAAA,UAAA,MAAU,SAAI,aAAnC,UAAA,GAAA,mBAGW,UAAA,EAAA,KAAA,EAAA,GAAA,CAFT,mBAAiE,QAAjE,cAAiE,gBAAzB,UAAA,MAAU,KAAK,GAAA,CAAA,GAC3C,UAAA,MAAU,UAAtB,UAAA,GAAA,mBAA2F,QAA3F,cAA2F,gBAA1B,UAAA,MAAU,MAAM,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,GAAA,EAAA,MAKjF,UAAA,GAAA,mBAAiE,QAAjE,cAAiE,gBAAzB,UAAA,MAAU,KAAK,GAAA,CAAA;IAI7C,QAAA,QAAZ,UAAA,GAAA,mBAAiE,QAAjE,cAAiE,gBAAd,QAAA,IAAI,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;IAI/C,QAAA,YADR,UAAA,GAAA,mBAeS,UAAA;;KAbN,OAAK,eAAA,CAAA,6BAAA,EAAA,qCAAuE,OAAA,MAAM,CAAA,CAAA;KACnF,MAAK;KACL,OAAM;KACN,cAAW;KACV,SAAK,cAAO,iBAAe,CAAA,MAAA,CAAA;IAEhB,GAAA,CAAA,CAAA,OAAA,SAAZ,UAAA,GAAA,mBAGM,OAHN,cAGM,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAFJ,mBAAyD,QAAA;KAAnD,GAAE;KAAI,GAAE;KAAI,OAAM;KAAK,QAAO;KAAK,IAAG;KAAI,IAAG;IACnD,GAAA,MAAA,EAAA,GAAA,mBAAoE,QAAA,EAA9D,GAAE,0DAAyD,GAAA,MAAA,EAAA,CAAA,EAAA,CAAA,MAEnE,UAAA,GAAA,mBAEM,OAFN,eAEM,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CADJ,mBAAoC,YAAA,EAA1B,QAAO,iBAAgB,GAAA,MAAA,EAAA,CAAA,EAAA,CAAA,EAAA,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;IAGzB,OAAA,SAAZ,UAAA,GAAA,mBAAoG,QAApG,eAAkE,YAAO,gBAAG,eAAA,KAAc,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;EE7K9F,MAAM,QAAQ;EASd,MAAM,QAAQ,eAAe,MAAM,OAAO,KAAK,KAAK,EAAE;EAEtD,MAAM,QAAQ,eAAe;GAC3B,MAAM,QAAQ,0BAA0B,KAAK,MAAM,KAAK;GACxD,OAAO,QAAQ;IAAE,MAAM,MAAM;IAAI,QAAQ,MAAM;GAAG,IAAI;IAAE,MAAM,MAAM;IAAO,QAAQ;GAAG;EACxF,CAAC;;GAIC,OAAA,UAAA,GAAA,mBAaO,QAAA;IAZJ,OAAK,eAAA;;KAAyD,qBAAA,QAAA;KAAsC,qBAAA,QAAA;KAAmC,qBAAA,QAAA;KAAmC,qBAAA,QAAA;KAA4C,EAAA,2BAAA,CAAA,MAAA,MAAK;;IAQ3N,OAAO,MAAA,SAAS,KAAA;GAED,GAAA,CAAA,MAAA,SAAhB,UAAA,GAAA,mBAAoI,UAAA,EAAA,KAAA,EAAA,GAAA,CAA1G,gBAAA,gBAAA,MAAA,MAAM,IAAI,GAAA,CAAA,GAAc,MAAA,MAAM,UAAjB,UAAA,GAAA,mBAAkF,OAAlF,cAAkF,gBAArB,MAAA,MAAM,MAAM,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,GAAA,EAAA,MAChH,UAAA,GAAA,mBAA2C,UAAA,EAAA,KAAA,EAAA,GAAA,CAAvB,gBAAA,gBAAA,QAAA,SAAS,GAAA,CAAA,CAAA,GAAA,EAAA,EAAA,GAAA,IAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EE3BjC,MAAM,QAAQ;EAUd,MAAM,UAAU,eAAe,MAAM,SAAS,KAAK,KAAK,EAAE;EAC1D,MAAM,QAAQ,eAAe,CAAC,QAAQ,OAAO,MAAM,QAAQ,KAAK,CAAC,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC;EAE5F,SAAS,SAAS,GAA0B;GAC1C,OAAO,mBAAmB,CAAC;EAC7B;;GAIE,OAAA,UAAA,GAAA,YA2BY,wBA1BL,QAAA,SAAM,SAAA,KAAA,GAAA;IACV,OAAK,eAAA;;KAAsC,CAAA,QAAA,SAAM,6BAAA;KAAgE,sBAAA,QAAA;KAAoC,sBAAA,QAAA;KAA2C;MAAA,2BAAA,QAAA;MAAI,4BAAA,CAA+B,QAAA;KAAO;;IAO1O,OAAO,MAAA,SAAS,KAAA;;IAEjB,SAAA,cAcW,CAdK,QAAA,SAAhB,UAAA,GAAA,mBAcW,UAAA,EAAA,KAAA,EAAA,GAAA;KAbT,mBAUO,QAVP,cAUO,EATL,UAAA,IAAA,GAAA,mBAQW,UAAA,MAAA,WARmB,SAAS,QAAA,KAAO,IAA5B,MAAM,MAAC;MAA8B,OAAA,UAAA,GAAA,mBAAA,UAAA,EAAA,KAAA,EAAC,GAAA,CAC1C,KAAK,SAAI,aAArB,UAAA,GAAA,mBAA8F,QAA9F,cAA8F,gBAAnB,KAAK,IAAI,GAAA,CAAA,KACnE,KAAK,SAAI,eAA1B,UAAA,GAAA,mBAAuG,QAAvG,cAAuG,gBAAnB,KAAK,IAAI,GAAA,CAAA,KAC5E,KAAK,SAAI,iBAA1B,UAAA,GAAA,mBAA2G,QAA3G,cAA2G,gBAAnB,KAAK,IAAI,GAAA,CAAA,KAChF,KAAK,SAAI,WAA1B,UAAA,GAAA,mBAA+F,QAA/F,cAA+F,gBAAnB,KAAK,IAAI,GAAA,CAAA,KACpE,KAAK,SAAI,SAA1B,UAAA,GAAA,mBAA2F,QAA3F,cAA2F,gBAAnB,KAAK,IAAI,GAAA,CAAA,KAChE,KAAK,SAAI,YAA1B,UAAA,GAAA,mBAAiG,QAAjG,cAAiG,gBAAnB,KAAK,IAAI,GAAA,CAAA,KACtE,KAAK,SAAI,iBAA1B,UAAA,GAAA,mBAAsI,QAAtI,cAAsI,gBAAnB,KAAK,IAAI,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,GAAA,EAAA;;KAG9G,QAAA,UAAlB,UAAA,GAAA,YAA2D,oBAAA;;MAAhC,OAAO,QAAA;MAAQ,MAAK;;KAC/C,WAAQ,KAAA,QAAA,SAAA;IAEV,GAAA,EAAA,MAAA,UAAA,GAAA,mBAAmC,QAAA,cAAA,gBAAnB,QAAA,SAAS,GAAA,CAAA,EAAA,CAAA;;;;;;;;AE1D7B,SAAgB,wBAAwB,UAAuD;CAC7F,IAAI,CAAC,YAAY,SAAS,iBAAiB,GAAG,OAAO;CACrD,OAAO,KAAK,MAAM,MAAO,SAAS,iBAAiB,SAAS,gBAAiB,KAAK,GAAG,GAAG,CAAC;AAC3F;AAEA,SAAgB,yBAAyB,UAAuD;CAC9F,IAAI,CAAC,UAAU,OAAO;CACtB,OAAO,KAAK,IAAI,GAAG,SAAS,gBAAgB,SAAS,cAAc;AACrE;AAEA,SAAgB,iCACd,UACe;CACf,IAAI,CAAC,UAAU,OAAO;CACtB,MAAM,cAAc,yBAAyB,QAAQ;CACrD,IAAI,eAAe,GAAG,OAAO;CAE7B,MAAM,WAAW,SAAS;CAC1B,IAAI,OAAO,aAAa,YAAY,WAAW,GAAG,OAAO;CAEzD,MAAM,MAAM,SAAS,sBAAsB,QAAQ,SAAS,gBAAgB;CAC5E,IAAI,OAAO,QAAQ,YAAY,MAAM,GAAG,OAAO,cAAc;CAE7D,MAAM,UAAU,SAAS,mBAAmB;CAC5C,IAAI,UAAU,KAAK,SAAS,iBAAiB,GAC3C,OAAQ,UAAU,SAAS,iBAAkB;CAG/C,OAAO;AACT;AAEA,SAAgB,2BACd,UACA,sBAAM,IAAI,KAAK,GACF;CACb,IAAI,CAAC,UAAU,OAAO;CAEtB,MAAM,WAAW,WAAW,SAAS,qBAAqB;CAC1D,IAAI,UAAU,OAAO;CAErB,MAAM,YAAY,iCAAiC,QAAQ;CAC3D,IAAI,aAAa,QAAQ,aAAa,GAAG,OAAO;CAChD,OAAO,IAAI,KAAK,IAAI,QAAQ,IAAI,YAAY,GAAI;AAClD;AAEA,SAAgB,wBAAwB,UAA8D;CACpG,MAAM,YAAY,iCAAiC,QAAQ;CAC3D,IAAI,aAAa,QAAQ,aAAa,GAAG,OAAO;CAEhD,MAAM,UAAU,YAAY;CAC5B,IAAI,UAAU,GAAG,OAAO,GAAG,KAAK,MAAM,SAAS,EAAE;CACjD,IAAI,UAAU,IAAI,OAAO,IAAI,KAAK,MAAM,OAAO,EAAE;CAEjD,MAAM,QAAQ,KAAK,MAAM,UAAU,EAAE;CACrC,MAAM,OAAO,KAAK,MAAM,UAAU,EAAE;CACpC,OAAO,OAAO,IAAI,IAAI,MAAM,IAAI,KAAK,UAAU,IAAI,MAAM;AAC3D;AAEA,SAAgB,kBACd,UACA,QACA,UAAsC,CAAC,GACxB;CACf,MAAM,SAAS,2BAA2B,QAAQ;CAClD,IAAI,CAAC,QAAQ,OAAO;CACpB,OAAO,OAAO,mBAAmB,QAAQ;EACvC,MAAM;EACN,QAAQ;EACR,GAAG;CACL,CAAC;AACH;AAEA,SAAS,QAAQ,QAAsD;CACrE,IAAI,CAAC,QAAQ,QAAQ,OAAO;CAC5B,MAAM,SAAS,OAAO,QAAO,UAAS,OAAO,SAAS,KAAK,KAAK,SAAS,CAAC;CAC1E,IAAI,OAAO,WAAW,GAAG,OAAO;CAChC,OAAO,OAAO,QAAQ,KAAK,UAAU,MAAM,OAAO,CAAC,IAAI,OAAO;AAChE;AAEA,SAAS,WAAW,OAAsD;CACxE,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,OAAO,iBAAiB,OAAO,QAAQ,IAAI,KAAK,KAAK;CAC3D,OAAO,OAAO,MAAM,KAAK,QAAQ,CAAC,IAAI,OAAO;AAC/C;AAEA,SAAS,MAAM,OAAe,KAAa,KAAqB;CAC9D,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECrEA,MAAM,QAAQ;EAQd,MAAM,UAAU,eAAe,wBAAwB,MAAM,QAAQ,CAAC;EACtE,MAAM,iBAAiB,eAAe,wBAAwB,MAAM,QAAQ,CAAC;EAC7E,MAAM,WAAW,eAAe,kBAAkB,MAAM,QAAQ,CAAC;EACjE,MAAM,YAAY,eAAe;GAC/B,IAAI,MAAM,SAAS,OAAO;GAC1B,OAAQ,MAAM,iBAAiB,eAAe,SAAW,MAAM,WAAW,SAAS;EACrF,CAAC;;GAIC,OAAA,UAAA,GAAA,mBA8BM,OA9BN,cA8BM;IA7BJ,mBAOM,OAPN,cAOM;KANJ,mBAGO,QAHP,cAGO,CAFL,mBAA+G,QAA/G,cAA+G,gBAAhE,QAAA,SAAS,cAAc,IAAG,QAAG,gBAAG,QAAA,SAAS,aAAa,GAAA,CAAA,GAAU,gBAAA,MAC/G,gBAAG,QAAA,KAAK,GAAA,CAAA,CAAA,CAAA;KAEE,QAAA,SAAM,KAAlB,UAAA,GAAA,mBAAyF,QAAzF,cAAyF,gBAAvB,QAAA,MAAM,IAAG,WAAO,CAAA,KAAA,mBAAA,IAAA,IAAA;KAClF,mBAAmE,QAAnE,cAAmE,gBAAlB,QAAA,KAAO,IAAG,KAAC,CAAA;;IAG9D,YAIE,qBAAA;KAHC,OAAO,QAAA;KACR,MAAK;KACL,cAAW;;IAGF,UAAA,SAAX,UAAA,GAAA,mBAaM,OAbN,cAaM,CAXI,QAAA,iBAAiB,eAAA,SADzB,UAAA,GAAA,mBAKO,QALP,cAKO,gBADF,eAAA,KAAc,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,GAGX,QAAA,WAAW,SAAA,SADnB,UAAA,GAAA,mBAKO,QALP,cAGC,UACK,gBAAG,SAAA,KAAQ,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;;;;;;;;;;;;;;;;;;;EEvDvB,MAAM,QAAQ;EAKd,MAAM,cAA6E;GACjF,SAAS;IAAE,OAAO;IAAW,MAAM;GAAU;GAC7C,SAAS;IAAE,OAAO;IAAW,MAAM;GAAU;GAC7C,MAAM;IAAE,OAAO;IAAQ,MAAM;GAAQ;GACrC,OAAO;IAAE,OAAO;IAAS,MAAM;GAAQ;GACvC,WAAW;IAAE,OAAO;IAAa,MAAM;GAAU;GACjD,cAAc;IAAE,OAAO;IAAW,MAAM;GAAQ;GAChD,OAAO;IAAE,OAAO;IAAkB,MAAM;GAAQ;GAChD,UAAU;IAAE,OAAO;IAAY,MAAM;GAAQ;EAC/C;;EAGA,MAAM,aAA4D;GAChE,cAAc;GACd,OAAO;GACP,UAAU;EACZ;EAEA,MAAM,kBAAkB,eAAe,MAAM,MAAM,YAAY,CAAC;EAChE,MAAM,SAAS,eAAe;GAC5B,MAAM,QAAQ,YAAY,gBAAgB;GAC1C,MAAM,MAAM,gBAAgB;GAC5B,OAAO,SAAS;IAAE,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,YAAY,IAAI,IAAI,MAAM,CAAC;IAAG,MAAM;GAAQ;EACrF,CAAC;EACD,MAAM,cAAc,eAAe,MAAM,oBAAoB,gBAAgB,UAAU,SAAS;;GAI9F,OAAA,UAAA,GAAA,mBAUO,QAAA,EATL,OAAK,eAAA,CAAC,yBAAuB;IACa,0BAAA,OAAA,MAAO;IAAgD,EAAA,gCAAA,YAAA,MAAW;IAAU,WAAW,gBAAA;GAMjI,CAAA,CAAA,EAAA,GAAA,CAAA,OAAA,OAAA,OAAA,KAAA,mBAA8D,QAAA;IAAxD,OAAM;IAA6B,eAAY;GAAS,GAAA,MAAA,EAAA,IAAA,gBAAA,MAC9D,gBAAG,QAAA,SAAS,OAAA,MAAO,KAAK,GAAA,CAAA,CAAA,GAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EEvB5B,MAAM,QAAQ;;EAMd,MAAM,WAAW,IAAkD,IAAI;EACvE,MAAM,iBAAiB,IAAI,EAAE;EAE7B,MAAM,OAAO,eAAe,MAAM,OAAO,KAAK,MAAM,WAAW;GAAE,OAAO;GAAO,OAAO,KAAK;EAAM,EAAE,CAAC;EACpG,MAAM,aAAa,eAAe;GAChC,MAAM,OAAO,SAAS;GAEtB,OADiB,MAAM,OAAO,SAAS,MAAM,UAAW,KAAK,UAAU,MAAM,QAAQ,CAAC,KAAK,IAAI,CAAC,CACzF,CAAA,CAAS,MAAM,cAAc,MAAM;EAC5C,CAAC;EACD,MAAM,QAAQ,eAAe,MAAM,OAAO,WAAW,MAAM;EAE3D,SAAS,UAAU,OAAe;GAChC,MAAM,QAAQ,MAAM,OAAO,MAAM,EAAE,SAAS;GAC5C,SAAS,QAAQ;IAAE;IAAO,YAAY,MAAM,OAAO,MAAM,GAAG,KAAK,CAAC,CAAC,QAAO,SAAQ,KAAK,UAAU,KAAK,CAAC,CAAC;GAAO;EACjH;EAEA,MAAM,SAAS,eAA+C;GAC5D,MAAM,UAAU,MAAM;GACtB,IAAI,SAAS,SAAS,SAAS,OAAO;IAAE,MAAM;IAAS,SAAS,QAAQ;IAAS,MAAM,QAAQ;GAAK;GACpG,IAAI,SAAS,SAAS,QAAQ,OAAO;IAAE,MAAM;IAAQ,MAAM,QAAQ;GAAK;GACxE,IAAI,SAAS,SAAS,QAAQ,OAAO;IAAE,MAAM;IAAQ,OAAO,QAAQ;GAAM;GAC1E,OAAO;EACT,CAAC;EAED,MAAM,MAAM,eAAgB,MAAM,OAAO,SAAS,QAAQ,MAAM,QAAQ,IAAK;EAC7E,MAAM,SAAS,eAAe,IAAI,OAAO,OAAO,MAAK,SAAQ,KAAK,SAAS,eAAe,KAAK,KAAK,IAAI,OAAO,OAAO,EAAE;EACxH,MAAM,SAAS,eAAgB,IAAI,SAAS,OAAO,QAC/C,CAAC;GAAE,MAAM;GAAgB,GAAG,IAAI,MAAM;GAAY,GAAG,OAAO,MAAM;GAAQ,MAAM,OAAO,MAAM;EAAK,CAAC,IACnG,CAAC,CAAE;EACP,MAAM,SAAS,gBAAgB;GAC7B,YAAY;GACZ,UAAU,CAAC,sBAAsB;GACjC,QAAQ;IAAE,GAAG;IAAG,GAAG;IAAG,GAAG;IAAI,GAAG;GAAG;GACnC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,OAAO,MAAM,OAAO,OAAO,QAAQ,IAAI,OAAO,MAAM,KAAK,EAAE,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,EAAE,EAAE;EAC3H,EAAE;EAEF,SAAS,YAAY,OAAiD;GACpE,OAAO,UAAU,OAAO,MAAM,OAAO,KAAK;EAC5C;;GAIE,OAAA,UAAA,GAAA,YAyBc,qBAAA;IAzBA,OAAO,QAAA;IAAQ,OAAO,QAAA;IAAM,SAAQ;IAAQ,OAAM;;IAC9D,SAAA,cAGE,CAHF,YAGE,kBAAA;KAFC,eAAa,WAAA;KAAa,SAAS,KAAA;KAAM,OAAM;KAC/C,uBAAkB,OAAA,OAAA,OAAA,MAAA,WAAE,UAAU,OAAO,MAAM,CAAA;IAGpC,GAAA,MAAA,GAAA,CAAA,eAAA,SAAA,CAAA,GAAA,MAAA,OAAO,SAAI,QAArB,UAAA,GAAA,mBAKK,MALL,cAKK,EAJH,UAAA,IAAA,GAAA,mBAGM,UAAA,MAAA,WAH+B,MAAA,MAAM,QAAK,CAAlC,KAAK,QAAQ,UAAK;KAAhC,OAAA,UAAA,GAAA,mBAGM,OAAA;MAH6C,KAAK;MAAO,OAAM;KACnE,GAAA,CAAA,mBAAqD,MAArD,cAAqD,gBAAX,GAAG,GAAA,CAAA,GAC7C,mBAAsE,MAAtE,cAAsE,gBAA1B,YAAY,KAAK,CAAA,GAAA,CAAA,CAAA,CAAA;IAIjD,CAAA,GAAA,GAAA,EAAA,CAAA,KAAA,OAAA,SAAhB,UAAA,GAAA,mBAEM,OAFN,cAEM,CADJ,YAAgE,iCAAA;KAAtC,QAAQ,OAAA;KAAS,YAAY;IAGzC,GAAA,MAAA,GAAA,CAAA,QAAA,CAAA,CAAA,CAAA,KAAA,IAAA,SAAhB,UAAA,GAAA,mBAOM,OAPN,cAOM,CALI,IAAA,MAAI,OAAO,SAAM,KADzB,UAAA,GAAA,YAIE,0BAAA;;KAH6B,OAAM;KAClC,eAAa,OAAA,OAAQ,QAAI;KAAS,SAAS,IAAA,MAAI,OAAO,KAAI,SAAQ,KAAK,IAAI;KAAG,MAAK;KACnF,uBAAkB,OAAA,OAAA,OAAA,MAAA,WAAE,eAAA,QAAiB,OAAO,MAAM;IAErD,GAAA,MAAA,GAAA,CAAA,eAAA,SAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,GAAA,YAA4H,qBAAA;KAA9G,MAAM,OAAA;KAAS,QAAQ,OAAA;KAAQ,SAAQ;KAAS,QAAQ;KAAM,iBAAe;KAAQ,cAAY,IAAA,MAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EE/EzH,MAAM,QAAQ;EAUd,MAAM,OAAO;EAMb,MAAM,aAAa,eAAe,MAAM,YAAY,KAAK;EACzD,MAAM,WAAW,eAAe,MAAM,YAAY,GAAG;EAErD,MAAM,WAAW,eAAe;GAC9B,IAAI,CAAC,MAAM,YAAY,SAAS,CAAC,MAAM,YAAY,KAAK,OAAO;GAC/D,OAAO,gBAAgB,MAAM,WAAW,OAAO,MAAM,WAAW,GAAG;EACrE,CAAC;EAED,MAAM,YAAY,eAAe;GAC/B,IAAI,CAAC,MAAM,YAAY,SAAS,CAAC,MAAM,YAAY,KAAK,OAAO;GAC/D,OAAO,YAAY,MAAM,WAAW,OAAO,MAAM,WAAW,GAAG,KAAK;EACtE,CAAC;EAGD,MAAM,gBAAgB,eAAe;GACnC,IAAI,SAAS,UAAU,QAAQ,UAAU,OAAO,OAAO;GACvD,MAAM,QAAQ,KAAK,MAAM,SAAS,QAAQ,EAAE;GAC5C,MAAM,UAAU,SAAS,QAAQ;GACjC,OAAO,CAAC,SAAS,GAAG,MAAM,KAAK,WAAW,GAAG,QAAQ,KAAK,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EACtF,CAAC;EAED,MAAM,UAAU,eAAe,CAAC,UAAU,KAAK;EAE/C,MAAM,UAAU,UAAU,KAAK,mBAAmB,KAAK,GAAG,EAAE,WAAW,KAAK,CAAC;EAE7E,SAAa,EAAE,QAAQ,CAAC;EAExB,MAAM,SAAS,eAAe;GAC5B,IAAI,MAAM,YAAY,OAAO,OAAO,MAAM,WAAW;GACrD,OAAO,MAAM;EACf,CAAC;EAED,SAAS,cAAc,OAA2B;GAChD,IAAI,CAAC,OAAO;IACV,KAAK,qBAAqB,KAAA,CAAS;IACnC;GACF;GACA,MAAM,MAAM,MAAM,YAAY,OAAO;GACrC,KAAK,qBAAqB;IAAE,OAAO;IAAO;GAAI,CAAC;EACjD;EAEA,SAAS,YAAY,OAA2B;GAC9C,IAAI,CAAC,OAAO;IACV,KAAK,qBAAqB,KAAA,CAAS;IACnC;GACF;GACA,MAAM,QAAQ,MAAM,YAAY,SAAS;GACzC,KAAK,qBAAqB;IAAE;IAAO,KAAK;GAAM,CAAC;EACjD;;GAIE,OAAA,UAAA,GAAA,mBA+CM,OA/CN,cA+CM;IA9CJ,mBAeM,OAfN,cAeM,CAdJ,YAaE,oBAAA;KAZA,MAAK;KACJ,eAAa,WAAA;KACb,UAAU,QAAA;KACV,SAAS,QAAA,WAAW,UAAA;KACpB,MAAM,QAAA;KACN,KAAK,QAAA;KACL,KAAK,QAAA;KACL,MAAM,QAAA;KACN,QAAQ,QAAA;KACR,kBAAgB,QAAA;KACjB,aAAY;KACX,uBAAoB;;;;;;;;;;;;IAIzB,OAAA,OAAA,OAAA,KAAA,mBAAoE,QAAA;KAA9D,OAAM;KAA6B,eAAY;IAAO,GAAA,KAAC,EAAA;IAE7D,mBAeM,OAfN,cAeM,CAdJ,YAaE,oBAAA;KAZA,MAAK;KACJ,eAAa,SAAA;KACb,UAAU,QAAA;KACV,SAAS,QAAA,WAAW,UAAA;KACpB,MAAM,QAAA;KACN,KAAK,OAAA;KACL,KAAK,QAAA;KACL,MAAM,QAAA;KACN,QAAQ,QAAA;KACR,kBAAgB,QAAA;KACjB,aAAY;KACX,uBAAoB;;;;;;;;;;;;IAKjB,QAAA,gBADR,UAAA,GAAA,mBAKO,QAAA;;KAHJ,OAAK,eAAA,CAAA,6BAAgC,UAAA,QAAS,qCAAA,EAAA,CAAA;IAE5C,GAAA,gBAAA,cAAA,KAAa,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;IAGT,UAAA,SAAT,UAAA,GAAA,mBAEI,KAFJ,cAAgE,qCAEhE,KAAA,mBAAA,IAAA,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EEnHJ,MAAM,QAAQ;EASd,MAAM,OAAO;EAKb,MAAM,sBAAsD;GAC1D,aAAa;GACb,UAAU;GACV,eAAe;GACf,WAAW;EACb;EAEA,MAAM,qBAAqB,eAAe,MAAM,eAAe,oBAAoB,MAAM,OAAO;EAEhG,MAAM,cAAc,eAAe;GACjC;GACA,uBAAuB,MAAM;GAC7B,EAAE,+BAA+B,MAAM,QAAQ;EACjD,CAAC;EAED,SAAS,WAAW,OAAmB;GACrC,MAAM,gBAAgB;GACtB,KAAK,MAAM;EACb;EAEA,SAAS,cAAc;GACrB,KAAK,OAAO;EACd;;GAKa,OAAA,QAAA,WAAX,UAAA,GAAA,mBAqBM,OAAA;;IArBe,OAAK,eAAE,YAAA,KAAW;IAAG,SAAO;;IAC/C,WAEO,KAAA,QAAA,UAAA,CAAA,SAAA,CADL,mBAAiG,QAAA,EAA1F,OAAK,eAAA,CAAA,kCAAA,mCAAwE,QAAA,QAAM,CAAA,EAAA,GAAA,MAAA,CAAA,CAAA,CAAA;IAG5F,mBAAwD,QAAxD,YAAwD,gBAAd,QAAA,IAAI,GAAA,CAAA;IAE9C,mBAEO,QAAA,EAFA,OAAK,eAAA,CAAA,oCAAA,qCAA4E,QAAA,QAAM,CAAA,EACzF,GAAA,gBAAA,mBAAA,KAAkB,GAAA,CAAA;IAGvB,WASO,KAAA,QAAA,UAAA,CAAA,SAAA,CAPG,QAAA,kBADR,UAAA,GAAA,mBAOS,UAAA;;KALP,MAAK;KACL,OAAM;KACL,SAAO;IACT,GAAA,QAED,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA;GAKJ,GAAA,CAAA,MAAA,UAAA,GAAA,mBAqEM,OAAA;;IArEO,OAAK,eAAE,YAAA,KAAW;IAAG,SAAO;GACvC,GAAA,CAAA,mBAmEM,OAnEN,YAmEM;IAlEJ,mBAgCM,OAhCN,YAgCM,CA9BJ,mBASM,OATN,YASM,CARJ,WAOO,KAAA,QAAA,SAAA,CAAA,SAAA,CANM,QAAA,SAAX,UAAA,GAAA,mBAA6C,OAAA;;KAA1B,KAAK,QAAA;KAAQ,KAAK,QAAA;IACrC,GAAA,MAAA,GAAA,UAAA,MAAA,UAAA,GAAA,mBAIM,OAJN,YAIM,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAHJ,mBAEM,OAAA;KAFD,MAAK;KAAO,QAAO;KAAe,SAAQ;IAC7C,GAAA,CAAA,mBAA4a,QAAA;KAAta,kBAAe;KAAQ,mBAAgB;KAAQ,gBAAa;KAAM,GAAE;IAOlF,CAAA,CAAA,GAAA,EAAA,CAAA,EAAA,CAAA,EAAA,CAAA,CAAA,CAAA,GAAA,mBAiBM,OAjBN,YAiBM;KAhBJ,mBASM,OATN,YASM,CARJ,mBAAoD,MAApD,YAAoD,gBAAZ,QAAA,IAAI,GAAA,CAAA,GAE5C,WAKO,KAAA,QAAA,UAAA,CAAA,SAAA,CAJL,mBAGM,OAHN,aAGM,CAFJ,mBAAiG,QAAA,EAA1F,OAAK,eAAA,CAAA,kCAAA,mCAAwE,QAAA,QAAM,CAAA,EAAA,GAAA,MAAA,CAAA,GAC1F,mBAA8E,QAA9E,aAA8E,gBAA5B,mBAAA,KAAkB,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA;KAKjE,QAAA,eAAT,UAAA,GAAA,mBAAmF,KAAnF,aAAmF,gBAAlB,QAAA,WAAW,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;KAEnE,QAAA,YAAT,UAAA,GAAA,mBAAoF,KAApF,aAAwD,eAAU,gBAAG,QAAA,QAAQ,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;KAEpE,QAAA,iBAAT,UAAA,GAAA,mBAA0G,KAA1G,aAAmE,qBAAgB,gBAAG,QAAA,aAAa,GAAA,CAAA,KAAA,mBAAA,IAAA,IAAA;;IAK5F,QAAA,MAAM,SAAM,KAAQA,KAAAA,OAAO,SAAtC,UAAA,GAAA,mBAOM,OAPN,aAOM,CANJ,WAKO,KAAA,QAAA,SAAA,CAAA,SAAA,EAJL,UAAA,IAAA,GAAA,mBAGM,UAAA,MAAA,WAHc,QAAA,QAAR,SAAI;KAAhB,OAAA,UAAA,GAAA,mBAGM,OAAA;MAHsB,KAAK,KAAK;MAAO,OAAM;KACjD,GAAA,CAAA,mBAAoE,QAApE,aAAoE,gBAApB,KAAK,KAAK,GAAA,CAAA,GAC1D,mBAAoE,QAApE,aAAoE,gBAApB,KAAK,KAAK,GAAA,CAAA,CAAA,CAAA;;IAMrD,QAAA,KAAK,SAAM,KAAQ,QAAA,kBAAkBA,KAAAA,OAAO,UAAvD,UAAA,GAAA,mBAoBM,OApBN,aAoBM,CAnBJ,mBAEM,OAFN,aAEM,EADJ,UAAA,IAAA,GAAA,mBAAuE,UAAA,MAAA,WAA/C,QAAA,OAAP,QAAG;KAApB,OAAA,UAAA,GAAA,YAAuE,kBAAA;MAAxC,KAAK;MAAK,MAAK;;MAAK,SAAA,cAAS,CAAN,gBAAA,gBAAA,GAAG,GAAA,CAAA,CAAA,CAAA;;;IAG3D,CAAA,GAAA,GAAA,EAAA,CAAA,GAAA,mBAcM,OAdN,aAcM,CAbJ,WAYO,KAAA,QAAA,UAAA,CAAA,SAAA,CAVG,QAAA,kBADR,UAAA,GAAA,mBAUS,UAAA;;KARP,MAAK;KACL,OAAM;KACL,SAAO;IACT,GAAA,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAA,gBAAA,cAEC,EAAA,GAAA,mBAEM,OAAA;KAFD,OAAM;KAAK,QAAO;KAAK,MAAK;KAAO,QAAO;KAAe,SAAQ;IACpE,GAAA,CAAA,mBAAyF,QAAA;KAAnF,kBAAe;KAAQ,mBAAgB;KAAQ,gBAAa;KAAI,GAAE;;;;;;;;AEpIxF,IAAa,kBAAoC;CAC/C;EAAE,OAAO;EAAM,QAAQ;CAAE;CACzB;EAAE,OAAO;EAAM,QAAQ;CAAE;CACzB;EAAE,OAAO;EAAS,QAAQ;CAAW;CACrC;EAAE,OAAO;EAAO,QAAQ;CAAG;AAC7B;AAEA,IAAa,gBAAgB;CAAC;CAAM;CAAM;CAAM;AAAI;AAEpD,SAAS,cAAc,QAAqC;CAC1D,OAAO,WAAW,aAAa,KAAK,KAAK,EAAE,IAAI;AACjD;AAEA,SAAS,mBAAmB,OAAe,SAAiB,GAAW;CACrE,IAAI,UAAU,GAAG,OAAO;CACxB,MAAM,YAAY,KAAK,MAAM,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,CAAC,IAAI;CAC5D,MAAM,SAAS,KAAK,IAAI,IAAI,SAAS,SAAS;CAC9C,OAAO,KAAK,MAAM,QAAQ,MAAM,IAAI;AACtC;AAEA,SAAgB,uBACd,oBACA,OACA,QACA,oBAA4B,GACd;CACd,IAAI,QAAQ,KAAK,sBAAsB,GAAG,OAAO,CAAC;CAElD,MAAM,UAAU,cAAc,MAAM;CACpC,MAAM,SAAuB,CAAC;CAE9B,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KACzB,OAAO,KAAK;EACV,OAAO,mBAAmB,qBAAqB,KAAK,IAAI,SAAS,CAAC,CAAC;EACnE,YAAY;CACd,CAAC;CAGH,OAAO;AACT;;AAcA,SAAgB,mBAA2C;CACzD,SAAS,eACP,OACA,OACA,QACA,oBAA4B,GACd;EACd,OAAO,uBAAuB,OAAO,OAAO,QAAQ,iBAAiB;CACvE;CAEA,SAAS,WAAW,QAAsB,QAAwB,QAAsB;EACtF,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,GAAG,MAC1B,UAAU,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,KACrD;CACF;CAEA,SAAS,eAAe,QAA8B;EACpD,OAAO,OAAO,QAAQ,KAAK,MAAM,MAAM,EAAE,YAAY,CAAC;CACxD;CAEA,OAAO;EAAE;EAAgB;EAAY;CAAe;AACtD"}
|