@morscherlab/mint-sdk 1.0.39 → 1.0.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/{ExperimentPopover-DEzCbTqo.js → ExperimentPopover-8A4Rhffp.js} +1 -1
  2. package/dist/{ExperimentPopover-mzmSfAUp.js → ExperimentPopover-BbPkIFsI.js} +8 -2
  3. package/dist/ExperimentPopover-BbPkIFsI.js.map +1 -0
  4. package/dist/{ExperimentSelectorModal-Bn0Hmg07.js → ExperimentSelectorModal-B2qek_YG.js} +91 -46
  5. package/dist/ExperimentSelectorModal-B2qek_YG.js.map +1 -0
  6. package/dist/{ExperimentSelectorModal-BAIlIybO.js → ExperimentSelectorModal-BwPbQN1g.js} +1 -1
  7. package/dist/__tests__/components/AutoGroupModal.preview.test.d.ts +1 -0
  8. package/dist/__tests__/composables/autoGroup/classKey.test.d.ts +1 -0
  9. package/dist/__tests__/composables/autoGroup/groupTree.test.d.ts +1 -0
  10. package/dist/__tests__/composables/autoGroup/tokenLength.test.d.ts +1 -0
  11. package/dist/components/index.js +3 -3
  12. package/dist/{components-Cyi0IfRl.js → components-CJ2--4Ex.js} +5606 -5592
  13. package/dist/components-CJ2--4Ex.js.map +1 -0
  14. package/dist/composables/autoGroup/classKey.d.ts +1 -0
  15. package/dist/composables/autoGroup/index.d.ts +2 -1
  16. package/dist/composables/autoGroup/replicatePreGroup.d.ts +10 -12
  17. package/dist/composables/autoGroup/tokenLength.d.ts +17 -0
  18. package/dist/composables/index.js +2 -2
  19. package/dist/composables/useAutoGroup.d.ts +2 -0
  20. package/dist/{composables-CFSn4NN3.js → composables-DrE6OcZZ.js} +2 -2
  21. package/dist/{composables-CFSn4NN3.js.map → composables-DrE6OcZZ.js.map} +1 -1
  22. package/dist/index.js +5 -5
  23. package/dist/install.js +3 -3
  24. package/dist/styles.css +1497 -1453
  25. package/dist/types/auto-group.d.ts +19 -0
  26. package/dist/{useProtocolTemplates-CXP2ZosM.js → useProtocolTemplates-BbvlHoPD.js} +218 -90
  27. package/dist/useProtocolTemplates-BbvlHoPD.js.map +1 -0
  28. package/package.json +1 -1
  29. package/src/__tests__/components/AutoGroupModal.preview.test.ts +46 -0
  30. package/src/__tests__/composables/autoGroup/classKey.test.ts +25 -0
  31. package/src/__tests__/composables/autoGroup/fingerprint.test.ts +72 -0
  32. package/src/__tests__/composables/autoGroup/groupTree.test.ts +99 -0
  33. package/src/__tests__/composables/autoGroup/tokenLength.test.ts +85 -0
  34. package/src/__tests__/composables/useAutoGroup.test.ts +111 -19
  35. package/src/components/AutoGroupModal.vue +23 -19
  36. package/src/components/BaseModal.story.vue +7 -15
  37. package/src/components/ExperimentDataViewer.vue +1 -0
  38. package/src/components/ExperimentPopover.vue +6 -4
  39. package/src/components/ExperimentSelectorModal.vue +30 -3
  40. package/src/components/IconButton.story.vue +5 -0
  41. package/src/components/SampleSelector.vue +3 -2
  42. package/src/components/SampleSelectorSampleRow.vue +4 -2
  43. package/src/composables/autoGroup/classKey.ts +5 -2
  44. package/src/composables/autoGroup/columns.ts +2 -2
  45. package/src/composables/autoGroup/compose.ts +56 -0
  46. package/src/composables/autoGroup/fingerprint.ts +15 -1
  47. package/src/composables/autoGroup/index.ts +2 -0
  48. package/src/composables/autoGroup/replicatePreGroup.ts +34 -0
  49. package/src/composables/autoGroup/template.ts +2 -2
  50. package/src/composables/autoGroup/tokenLength.ts +53 -0
  51. package/src/composables/autoGroup/vocab.json +1 -2
  52. package/src/composables/useAutoGroup.ts +34 -13
  53. package/src/styles/components/auto-group-modal.css +7 -11
  54. package/src/styles/components/button.css +10 -3
  55. package/src/styles/components/modal.css +3 -0
  56. package/src/styles/components/sample-selector.css +17 -0
  57. package/src/styles/variables.css +8 -0
  58. package/src/types/auto-group.ts +19 -0
  59. package/dist/ExperimentPopover-mzmSfAUp.js.map +0 -1
  60. package/dist/ExperimentSelectorModal-Bn0Hmg07.js.map +0 -1
  61. package/dist/components-Cyi0IfRl.js.map +0 -1
  62. package/dist/useProtocolTemplates-CXP2ZosM.js.map +0 -1
@@ -866,7 +866,7 @@
866
866
  font-size: 0.8125rem;
867
867
  color: var(--mint-error);
868
868
  background: var(--mint-error-bg);
869
- border: 1px solid rgba(239, 68, 68, 0.3);
869
+ border: 1px solid var(--mint-error-border);
870
870
  border-radius: var(--radius);
871
871
  }
872
872
 
@@ -889,7 +889,7 @@
889
889
 
890
890
  .mint-auto-group__experiment-error {
891
891
  padding: 1.5rem;
892
- border: 1px solid rgba(239, 68, 68, 0.3);
892
+ border: 1px solid var(--mint-error-border);
893
893
  border-radius: var(--radius-md);
894
894
  background: var(--mint-error-bg);
895
895
  font-size: 0.8125rem;
@@ -925,7 +925,7 @@
925
925
  .mint-auto-group__link-btn:focus-visible {
926
926
  outline: 2px solid var(--color-primary);
927
927
  outline-offset: 1px;
928
- border-radius: 2px;
928
+ border-radius: var(--radius-sm);
929
929
  }
930
930
 
931
931
  .mint-auto-group__experiment-loaded {
@@ -1258,14 +1258,10 @@
1258
1258
  .mint-auto-group__preview-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
1259
1259
  .mint-auto-group__preview-panel { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; }
1260
1260
  .mint-auto-group__preview-panel h4 { margin: 0 0 10px; font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: .06em; }
1261
- .mint-auto-group__preview-group { border: 1px solid var(--border-color); border-radius: var(--radius); overflow: hidden; }
1262
- .mint-auto-group__preview-group[open] { background: var(--bg-secondary); }
1263
- .mint-auto-group__preview-group summary { display: flex; align-items: center; gap: 10px; cursor: pointer; font-size: 13px; padding: 0.5rem 0.875rem; }
1264
- .mint-auto-group__preview-group[open] summary { border-bottom: 1px solid var(--border); }
1265
- .mint-auto-group__preview-dot { width: 10px; height: 10px; border-radius: 9999px; flex-shrink: 0; }
1266
- .mint-auto-group__preview-count { margin-left: auto; font-family: 'Fira Code', monospace; font-size: 12px; color: var(--text-secondary); background: var(--bg-primary); border: 1px solid var(--border); padding: 2px 8px; border-radius: 9999px; }
1267
- .mint-auto-group__preview-samples { display: flex; flex-wrap: wrap; gap: 6px; padding: 0.5rem 0.875rem 0.625rem; }
1268
- .mint-auto-group__preview-sample { font-family: 'Fira Code', monospace; font-size: 11.5px; color: var(--text-primary); background: var(--bg-primary); border: 1px solid var(--border); padding: 3px 8px; border-radius: 9999px; }
1261
+ /* Nested experimental groups render as a collapsible SampleHierarchyTree; cap
1262
+ its height so a large batch scrolls inside the panel instead of the modal. */
1263
+ .mint-auto-group__preview-tree { max-height: 320px; overflow-y: auto; }
1264
+ .mint-auto-group__preview-empty { margin: 0; font-size: 13px; color: var(--text-muted); }
1269
1265
  .mint-auto-group__qc-chips { display: flex; flex-wrap: wrap; gap: 6px; }
1270
1266
  .mint-auto-group__qc-chip { background: var(--mint-warning-bg); color: #92400E; border: 1px solid var(--mint-warning-border); padding: 4px 10px; border-radius: 9999px; font-size: 12px; }
1271
1267
  .mint-auto-group__fingerprint { margin-top: 14px; }
@@ -78,10 +78,12 @@
78
78
 
79
79
  .mint-button--secondary {
80
80
  background-color: var(--bg-secondary);
81
- color: var(--text-primary);
81
+ color: var(--text-secondary-strong);
82
82
  border: 1px solid var(--border-color);
83
83
  /* Intentionally tighter + fainter than the colored variants —
84
- secondary shouldn't "float" the way filled buttons do. */
84
+ secondary shouldn't "float" the way filled buttons do. Regular (400)
85
+ weight de-emphasizes the label relative to the medium-weight filled variants. */
86
+ font-weight: 400;
85
87
  box-shadow: 0 1px 1px rgba(15, 23, 42, 0.03);
86
88
  }
87
89
 
@@ -125,7 +127,9 @@
125
127
 
126
128
  .mint-button--ghost {
127
129
  background-color: transparent;
128
- color: var(--text-primary);
130
+ color: var(--text-secondary-strong);
131
+ /* Lowest-emphasis variant: regular (400) weight, matching secondary. */
132
+ font-weight: 400;
129
133
  }
130
134
 
131
135
  .mint-button--ghost:hover:not(.mint-button--disabled) {
@@ -136,6 +140,9 @@
136
140
  .mint-button--sm {
137
141
  padding: 0.375rem 0.75rem;
138
142
  font-size: 0.875rem;
143
+ /* Small buttons live in dense/secondary contexts — regular (400) weight
144
+ keeps the compact label from reading too heavy. Applies to all variants. */
145
+ font-weight: 400;
139
146
  min-height: var(--form-height-sm);
140
147
  }
141
148
 
@@ -165,6 +165,9 @@
165
165
 
166
166
  .mint-modal__close {
167
167
  flex-shrink: 0;
168
+ /* Always pin the close affordance to the right edge of the header, even when
169
+ there is no title/subtitle (no flex-1 header-text to push it over). */
170
+ margin-left: auto;
168
171
  width: 1.75rem;
169
172
  height: 1.75rem;
170
173
  padding: 0;
@@ -239,6 +239,11 @@
239
239
  background-color: var(--bg-hover);
240
240
  }
241
241
 
242
+ .mint-sample-selector__expand-btn:focus-visible {
243
+ outline: none;
244
+ box-shadow: var(--focus-ring);
245
+ }
246
+
242
247
  .mint-sample-selector__expand-icon {
243
248
  width: 0.875rem;
244
249
  height: 0.875rem;
@@ -363,6 +368,11 @@
363
368
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08), 0 0 0 3px rgba(0, 0, 0, 0.06);
364
369
  }
365
370
 
371
+ .mint-sample-selector__color-dot--clickable:focus-visible {
372
+ outline: none;
373
+ box-shadow: var(--focus-ring);
374
+ }
375
+
366
376
  /* Count Badge */
367
377
  .mint-sample-selector__count-badge {
368
378
  font-size: 0.625rem;
@@ -396,6 +406,13 @@
396
406
  color: var(--mint-error);
397
407
  }
398
408
 
409
+ /* Reveal the hover-hidden delete button when reached by keyboard, and ring it. */
410
+ .mint-sample-selector__delete-btn:focus-visible {
411
+ outline: none;
412
+ opacity: 1;
413
+ box-shadow: var(--focus-ring);
414
+ }
415
+
399
416
  .mint-sample-selector__delete-btn--hidden {
400
417
  opacity: 0;
401
418
  }
@@ -22,6 +22,11 @@
22
22
  --text-primary: #1E293B;
23
23
  --text-secondary: #64748B;
24
24
  --text-muted: #94A3B8;
25
+ /* De-emphasized label for low-emphasis interactive controls (secondary/ghost
26
+ buttons). A true dark gray — softer than --text-primary's near-black — that
27
+ still clears WCAG AA (4.5:1) on the secondary hover surface (--bg-tertiary),
28
+ where --text-secondary would drop to ~4.35:1. */
29
+ --text-secondary-strong: #475569;
25
30
 
26
31
  /* Brand colors */
27
32
  --color-primary: #6366F1;
@@ -129,6 +134,9 @@ html.dark {
129
134
  --text-primary: #F8FAFC;
130
135
  --text-secondary: #94A3B8;
131
136
  --text-muted: #64748B;
137
+ /* Dark-mode analog: soft light-gray instead of near-white, AA-safe (7.2:1)
138
+ on the dark secondary hover surface (--bg-tertiary #334155). */
139
+ --text-secondary-strong: #CBD5E1;
132
140
 
133
141
  /* Scrollbar */
134
142
  --scrollbar-track: #1E293B;
@@ -1,4 +1,5 @@
1
1
  import type { SampleGroup } from './components'
2
+ import type { TreeNode } from './componentLabTypes'
2
3
 
3
4
  // ---- Legacy (kept for source-compatibility) ----
4
5
  export type OutlierAction = 'include' | 'exclude' | 'qc'
@@ -45,6 +46,12 @@ export interface SampleClass {
45
46
  members: number[]
46
47
  classTagPositions: number[]
47
48
  disposition: ClassDisposition
49
+ /**
50
+ * Token count shared by every member of this class. Set by `splitByTokenLength`
51
+ * after type classification so that samples with different field counts never
52
+ * share a schema — column index N then means the same field for every member.
53
+ */
54
+ tokenLength?: number
48
55
  }
49
56
 
50
57
  // ---- Column / role classifier ----
@@ -95,6 +102,9 @@ export interface ColumnInfo {
95
102
  export interface ClassSchema {
96
103
  classKind: SampleClassKind
97
104
  subKind?: string
105
+ /** Token count of the class this schema describes. Distinguishes schemas that
106
+ * share a (kind, subKind) but were split apart by token-length. */
107
+ tokenLength?: number
98
108
  columns: ColumnInfo[]
99
109
  groupBy: number[]
100
110
  replicateColumn?: number
@@ -119,6 +129,14 @@ export interface AutoGroupResult {
119
129
  excludedSamples: string[]
120
130
  schemas?: ClassSchema[]
121
131
  fingerprint?: SchemaFingerprint
132
+ /**
133
+ * Experimental (disposition === 'group') classes as a nested hierarchy:
134
+ * one root per class, then one level per enabled groupBy column, then the
135
+ * sample leaves. Parent nodes carry a sample-count `badge`; leaves carry the
136
+ * sample name plus the detected injection number (`metadata.injection` and as
137
+ * the leaf badge). Renders directly in `SampleHierarchyTree`.
138
+ */
139
+ groupTree?: TreeNode[]
122
140
  }
123
141
 
124
142
  export interface SchemaFingerprint {
@@ -126,6 +144,7 @@ export interface SchemaFingerprint {
126
144
  classes: Array<{
127
145
  kind: SampleClassKind
128
146
  subKind?: string
147
+ tokenLength?: number
129
148
  columns: Array<{
130
149
  name: string
131
150
  role: ColumnRole
@@ -1 +0,0 @@
1
- {"version":3,"file":"ExperimentPopover-mzmSfAUp.js","names":["$slots"],"sources":["../src/components/ConfirmDialog.vue","../src/components/ConfirmDialog.vue","../src/components/ExperimentPopover.vue","../src/components/ExperimentPopover.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Confirm/cancel dialog with danger, warning, and info variants; blocks close while loading. */\nimport { computed } from 'vue'\nimport type { ButtonVariant } from '../types'\nimport BaseModal from './BaseModal.vue'\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n modelValue: boolean\n title?: string\n subtitle?: string\n message?: string\n variant?: 'danger' | 'warning' | 'info'\n confirmLabel?: string\n cancelLabel?: string\n loading?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Confirm',\n variant: 'danger',\n confirmLabel: 'Confirm',\n cancelLabel: 'Cancel',\n loading: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n confirm: []\n cancel: []\n}>()\n\nfunction handleCancel() {\n emit('update:modelValue', false)\n emit('cancel')\n}\n\nfunction handleConfirm() {\n emit('confirm')\n}\n\nconst confirmVariant = computed<ButtonVariant>(() => {\n if (props.variant === 'info') return 'primary'\n if (props.variant === 'warning') return 'cta'\n return 'danger'\n})\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :subtitle=\"subtitle\"\n size=\"sm\"\n :closable=\"!loading\"\n :close-on-overlay=\"!loading\"\n :close-on-escape=\"!loading\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <div class=\"mint-confirm\">\n <!-- Icon is now opt-in via slot. Refresh design keeps the body focused on\n the message; variant intent is carried by the confirm button's color. -->\n <div\n v-if=\"$slots.icon\"\n :class=\"['mint-confirm__icon', `mint-confirm__icon--${variant}`]\"\n >\n <slot name=\"icon\" />\n </div>\n <p v-if=\"message\" class=\"mint-confirm__message\">{{ message }}</p>\n <slot />\n </div>\n\n <template #footer>\n <div class=\"mint-confirm__footer\">\n <BaseButton\n type=\"button\"\n variant=\"secondary\"\n size=\"md\"\n :disabled=\"loading\"\n @click=\"handleCancel\"\n >\n {{ cancelLabel }}\n </BaseButton>\n <BaseButton\n type=\"button\"\n :variant=\"confirmVariant\"\n size=\"md\"\n :loading=\"loading\"\n @click=\"handleConfirm\"\n >\n {{ confirmLabel }}\n </BaseButton>\n </div>\n </template>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/confirm-dialog.css';\n</style>\n","<script setup lang=\"ts\">\n/** Confirm/cancel dialog with danger, warning, and info variants; blocks close while loading. */\nimport { computed } from 'vue'\nimport type { ButtonVariant } from '../types'\nimport BaseModal from './BaseModal.vue'\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n modelValue: boolean\n title?: string\n subtitle?: string\n message?: string\n variant?: 'danger' | 'warning' | 'info'\n confirmLabel?: string\n cancelLabel?: string\n loading?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n title: 'Confirm',\n variant: 'danger',\n confirmLabel: 'Confirm',\n cancelLabel: 'Cancel',\n loading: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n confirm: []\n cancel: []\n}>()\n\nfunction handleCancel() {\n emit('update:modelValue', false)\n emit('cancel')\n}\n\nfunction handleConfirm() {\n emit('confirm')\n}\n\nconst confirmVariant = computed<ButtonVariant>(() => {\n if (props.variant === 'info') return 'primary'\n if (props.variant === 'warning') return 'cta'\n return 'danger'\n})\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :subtitle=\"subtitle\"\n size=\"sm\"\n :closable=\"!loading\"\n :close-on-overlay=\"!loading\"\n :close-on-escape=\"!loading\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <div class=\"mint-confirm\">\n <!-- Icon is now opt-in via slot. Refresh design keeps the body focused on\n the message; variant intent is carried by the confirm button's color. -->\n <div\n v-if=\"$slots.icon\"\n :class=\"['mint-confirm__icon', `mint-confirm__icon--${variant}`]\"\n >\n <slot name=\"icon\" />\n </div>\n <p v-if=\"message\" class=\"mint-confirm__message\">{{ message }}</p>\n <slot />\n </div>\n\n <template #footer>\n <div class=\"mint-confirm__footer\">\n <BaseButton\n type=\"button\"\n variant=\"secondary\"\n size=\"md\"\n :disabled=\"loading\"\n @click=\"handleCancel\"\n >\n {{ cancelLabel }}\n </BaseButton>\n <BaseButton\n type=\"button\"\n :variant=\"confirmVariant\"\n size=\"md\"\n :loading=\"loading\"\n @click=\"handleConfirm\"\n >\n {{ confirmLabel }}\n </BaseButton>\n </div>\n </template>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/confirm-dialog.css';\n</style>\n","<script setup lang=\"ts\">\n/** Floating popover showing the active experiment with save, detach, and select actions. */\nimport { ref, watch, onUnmounted } from 'vue'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport { formatExperimentStatus } from '../composables/experiment-utils'\nimport ConfirmDialog from './ConfirmDialog.vue'\n\ninterface Props {\n experimentName?: string\n experimentCode?: string\n experimentStatus?: string\n showSave?: boolean\n showDetach?: boolean\n saveDisabled?: boolean\n saveLoading?: boolean\n saveSuccessMessage?: string\n saveDisabledMessage?: string\n confirmSave?: boolean\n confirmTitle?: string\n confirmMessage?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showSave: false,\n showDetach: false,\n saveDisabled: false,\n saveLoading: false,\n confirmSave: true,\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n detach: []\n}>()\n\nconst { isOpen, rootRef: popoverRef, close, toggle } = useDropdownState({\n closeOnEscape: false,\n})\nconst showSuccess = ref(false)\nconst showConfirm = ref(false)\n\nfunction handleSelect() {\n emit('select')\n close()\n}\n\nfunction handleSave() {\n if (props.saveDisabled || props.saveLoading) return\n if (props.confirmSave) {\n showConfirm.value = true\n } else {\n emit('save')\n }\n}\n\nfunction handleConfirmSave() {\n showConfirm.value = false\n emit('save')\n}\n\nfunction handleDetach() {\n emit('detach')\n close()\n}\n\nlet successTimer: ReturnType<typeof setTimeout> | null = null\n\n// Show success state when saveSuccessMessage changes from empty to a value\nwatch(() => props.saveSuccessMessage, (msg) => {\n if (successTimer) clearTimeout(successTimer)\n if (msg) {\n showSuccess.value = true\n successTimer = setTimeout(() => {\n showSuccess.value = false\n successTimer = null\n }, 3000)\n }\n})\n\nonUnmounted(() => {\n if (successTimer) clearTimeout(successTimer)\n})\n</script>\n\n<template>\n <div ref=\"popoverRef\" class=\"mint-experiment-popover\">\n <!-- Split trigger: experiment pill + inline save -->\n <div\n :class=\"[\n 'mint-experiment-popover__split',\n { 'mint-experiment-popover__split--with-save': showSave && experimentName },\n ]\"\n >\n <!-- Left: experiment trigger (opens popover). Prefer the human-readable\n name; the formal code, when present, stays in the detail panel. -->\n <button\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__trigger',\n { 'mint-experiment-popover__trigger--active': isOpen },\n { 'mint-experiment-popover__trigger--empty': !experimentCode && !experimentName },\n ]\"\n :title=\"experimentName || experimentCode || undefined\"\n @click.stop=\"toggle\"\n >\n <!-- Flask icon -->\n <svg class=\"mint-experiment-popover__trigger-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n <!-- Name preferred, code as fallback, \"No experiment\" as empty state -->\n <span v-if=\"experimentName\" class=\"mint-experiment-popover__trigger-text\">{{ experimentName }}</span>\n <span v-else-if=\"experimentCode\" class=\"mint-experiment-popover__trigger-code\">{{ experimentCode }}</span>\n <span v-else class=\"mint-experiment-popover__trigger-text\">No experiment</span>\n <!-- Chevron -->\n <svg class=\"mint-experiment-popover__trigger-chevron\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <!-- Right: inline save button (direct action) -->\n <button\n v-if=\"showSave && experimentName\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__save-trigger',\n { 'mint-experiment-popover__save-trigger--loading': saveLoading },\n { 'mint-experiment-popover__save-trigger--success': showSuccess },\n { 'mint-experiment-popover__save-trigger--disabled': saveDisabled && !showSuccess },\n ]\"\n :disabled=\"saveDisabled && !showSuccess\"\n :title=\"saveDisabled && saveDisabledMessage ? saveDisabledMessage : showSuccess && saveSuccessMessage ? saveSuccessMessage : 'Save to Experiment'\"\n @click.stop=\"handleSave\"\n >\n <!-- Loading spinner -->\n <span v-if=\"saveLoading\" class=\"mint-experiment-popover__spinner--inline\" />\n <!-- Success check -->\n <svg v-else-if=\"showSuccess\" class=\"mint-experiment-popover__save-trigger-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2.5\" d=\"M5 13l4 4L19 7\" />\n </svg>\n <!-- Save icon -->\n <svg v-else class=\"mint-experiment-popover__save-trigger-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4\" />\n </svg>\n </button>\n </div>\n\n <!-- Popover panel -->\n <div v-if=\"isOpen\" class=\"mint-experiment-popover__panel\">\n <!-- Header -->\n <div class=\"mint-experiment-popover__header\">\n <div class=\"mint-experiment-popover__title\">Experiment</div>\n <div class=\"mint-experiment-popover__subtitle\">\n {{ experimentName ? 'Linked experiment context' : 'Link to an MINT experiment' }}\n </div>\n </div>\n\n <!-- No experiment selected -->\n <div v-if=\"!experimentName\" class=\"mint-experiment-popover__body\">\n <button type=\"button\" class=\"mint-experiment-popover__select-btn\" @click=\"handleSelect\">\n <svg width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\" />\n </svg>\n Select Experiment\n </button>\n </div>\n\n <!-- Experiment selected -->\n <div v-else class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card\">\n <div class=\"mint-experiment-popover__card-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=\"1.75\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <span v-if=\"experimentCode\" class=\"mint-experiment-popover__card-code\">{{ experimentCode }}</span>\n <div class=\"mint-experiment-popover__card-name\">{{ experimentName }}</div>\n <div v-if=\"experimentStatus\" class=\"mint-experiment-popover__card-status\">\n {{ formatExperimentStatus(experimentStatus) }}\n </div>\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n </div>\n\n <!-- Save confirmation dialog -->\n <ConfirmDialog\n v-model=\"showConfirm\"\n :title=\"confirmTitle ?? 'Save to Experiment'\"\n :message=\"confirmMessage ?? `Save current data to ${experimentName}?`\"\n variant=\"info\"\n confirm-label=\"Save\"\n :loading=\"saveLoading\"\n @confirm=\"handleConfirmSave\"\n />\n </div>\n</template>\n\n<style>\n@import '../styles/components/experiment-popover.css';\n</style>\n","<script setup lang=\"ts\">\n/** Floating popover showing the active experiment with save, detach, and select actions. */\nimport { ref, watch, onUnmounted } from 'vue'\nimport { useDropdownState } from '../composables/useDropdownState'\nimport { formatExperimentStatus } from '../composables/experiment-utils'\nimport ConfirmDialog from './ConfirmDialog.vue'\n\ninterface Props {\n experimentName?: string\n experimentCode?: string\n experimentStatus?: string\n showSave?: boolean\n showDetach?: boolean\n saveDisabled?: boolean\n saveLoading?: boolean\n saveSuccessMessage?: string\n saveDisabledMessage?: string\n confirmSave?: boolean\n confirmTitle?: string\n confirmMessage?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showSave: false,\n showDetach: false,\n saveDisabled: false,\n saveLoading: false,\n confirmSave: true,\n})\n\nconst emit = defineEmits<{\n select: []\n save: []\n detach: []\n}>()\n\nconst { isOpen, rootRef: popoverRef, close, toggle } = useDropdownState({\n closeOnEscape: false,\n})\nconst showSuccess = ref(false)\nconst showConfirm = ref(false)\n\nfunction handleSelect() {\n emit('select')\n close()\n}\n\nfunction handleSave() {\n if (props.saveDisabled || props.saveLoading) return\n if (props.confirmSave) {\n showConfirm.value = true\n } else {\n emit('save')\n }\n}\n\nfunction handleConfirmSave() {\n showConfirm.value = false\n emit('save')\n}\n\nfunction handleDetach() {\n emit('detach')\n close()\n}\n\nlet successTimer: ReturnType<typeof setTimeout> | null = null\n\n// Show success state when saveSuccessMessage changes from empty to a value\nwatch(() => props.saveSuccessMessage, (msg) => {\n if (successTimer) clearTimeout(successTimer)\n if (msg) {\n showSuccess.value = true\n successTimer = setTimeout(() => {\n showSuccess.value = false\n successTimer = null\n }, 3000)\n }\n})\n\nonUnmounted(() => {\n if (successTimer) clearTimeout(successTimer)\n})\n</script>\n\n<template>\n <div ref=\"popoverRef\" class=\"mint-experiment-popover\">\n <!-- Split trigger: experiment pill + inline save -->\n <div\n :class=\"[\n 'mint-experiment-popover__split',\n { 'mint-experiment-popover__split--with-save': showSave && experimentName },\n ]\"\n >\n <!-- Left: experiment trigger (opens popover). Prefer the human-readable\n name; the formal code, when present, stays in the detail panel. -->\n <button\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__trigger',\n { 'mint-experiment-popover__trigger--active': isOpen },\n { 'mint-experiment-popover__trigger--empty': !experimentCode && !experimentName },\n ]\"\n :title=\"experimentName || experimentCode || undefined\"\n @click.stop=\"toggle\"\n >\n <!-- Flask icon -->\n <svg class=\"mint-experiment-popover__trigger-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"1.75\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n <!-- Name preferred, code as fallback, \"No experiment\" as empty state -->\n <span v-if=\"experimentName\" class=\"mint-experiment-popover__trigger-text\">{{ experimentName }}</span>\n <span v-else-if=\"experimentCode\" class=\"mint-experiment-popover__trigger-code\">{{ experimentCode }}</span>\n <span v-else class=\"mint-experiment-popover__trigger-text\">No experiment</span>\n <!-- Chevron -->\n <svg class=\"mint-experiment-popover__trigger-chevron\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n\n <!-- Right: inline save button (direct action) -->\n <button\n v-if=\"showSave && experimentName\"\n type=\"button\"\n :class=\"[\n 'mint-experiment-popover__save-trigger',\n { 'mint-experiment-popover__save-trigger--loading': saveLoading },\n { 'mint-experiment-popover__save-trigger--success': showSuccess },\n { 'mint-experiment-popover__save-trigger--disabled': saveDisabled && !showSuccess },\n ]\"\n :disabled=\"saveDisabled && !showSuccess\"\n :title=\"saveDisabled && saveDisabledMessage ? saveDisabledMessage : showSuccess && saveSuccessMessage ? saveSuccessMessage : 'Save to Experiment'\"\n @click.stop=\"handleSave\"\n >\n <!-- Loading spinner -->\n <span v-if=\"saveLoading\" class=\"mint-experiment-popover__spinner--inline\" />\n <!-- Success check -->\n <svg v-else-if=\"showSuccess\" class=\"mint-experiment-popover__save-trigger-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2.5\" d=\"M5 13l4 4L19 7\" />\n </svg>\n <!-- Save icon -->\n <svg v-else class=\"mint-experiment-popover__save-trigger-icon\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4\" />\n </svg>\n </button>\n </div>\n\n <!-- Popover panel -->\n <div v-if=\"isOpen\" class=\"mint-experiment-popover__panel\">\n <!-- Header -->\n <div class=\"mint-experiment-popover__header\">\n <div class=\"mint-experiment-popover__title\">Experiment</div>\n <div class=\"mint-experiment-popover__subtitle\">\n {{ experimentName ? 'Linked experiment context' : 'Link to an MINT experiment' }}\n </div>\n </div>\n\n <!-- No experiment selected -->\n <div v-if=\"!experimentName\" class=\"mint-experiment-popover__body\">\n <button type=\"button\" class=\"mint-experiment-popover__select-btn\" @click=\"handleSelect\">\n <svg width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 4v16m8-8H4\" />\n </svg>\n Select Experiment\n </button>\n </div>\n\n <!-- Experiment selected -->\n <div v-else class=\"mint-experiment-popover__body\">\n <div class=\"mint-experiment-popover__card\">\n <div class=\"mint-experiment-popover__card-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=\"1.75\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n </div>\n <div class=\"mint-experiment-popover__card-info\">\n <span v-if=\"experimentCode\" class=\"mint-experiment-popover__card-code\">{{ experimentCode }}</span>\n <div class=\"mint-experiment-popover__card-name\">{{ experimentName }}</div>\n <div v-if=\"experimentStatus\" class=\"mint-experiment-popover__card-status\">\n {{ formatExperimentStatus(experimentStatus) }}\n </div>\n </div>\n </div>\n <div class=\"mint-experiment-popover__card-actions\">\n <button type=\"button\" class=\"mint-experiment-popover__change-btn\" @click=\"handleSelect\">\n Change\n </button>\n <button v-if=\"showDetach\" type=\"button\" class=\"mint-experiment-popover__detach-btn\" @click=\"handleDetach\">\n Detach\n </button>\n </div>\n </div>\n </div>\n\n <!-- Save confirmation dialog -->\n <ConfirmDialog\n v-model=\"showConfirm\"\n :title=\"confirmTitle ?? 'Save to Experiment'\"\n :message=\"confirmMessage ?? `Save current data to ${experimentName}?`\"\n variant=\"info\"\n confirm-label=\"Save\"\n :loading=\"saveLoading\"\n @confirm=\"handleConfirmSave\"\n />\n </div>\n</template>\n\n<style>\n@import '../styles/components/experiment-popover.css';\n</style>\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ExperimentSelectorModal-Bn0Hmg07.js","names":[],"sources":["../src/components/BasePill.vue","../src/components/BasePill.vue","../src/components/Skeleton.vue","../src/components/Skeleton.vue","../src/components/EmptyState.vue","../src/components/EmptyState.vue","../src/components/ExperimentCodeBadge.vue","../src/components/ExperimentCodeBadge.vue","../src/components/ExperimentSelectorModal.vue","../src/components/ExperimentSelectorModal.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/** Compact label for tags, status indicators, and badges; supports removable and icon slots. */\nimport type { PillVariant, PillColor, PillSize } from '../types'\n\n/**\n * BasePill - Compact label component for tags, status indicators, and badges.\n *\n * @example\n * ```vue\n * <BasePill variant=\"success\">Active</BasePill>\n * <BasePill variant=\"warning\" removable @remove=\"handleRemove\">Tag</BasePill>\n * <BasePill :icon=\"true\">\n * <template #icon><CheckIcon /></template>\n * Verified\n * </BasePill>\n * ```\n */\ninterface Props {\n /** Visual style variant */\n variant?: PillVariant\n /** Semantic color modifier — use with variant=\"outline\" for colored outlines */\n color?: PillColor\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}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'default',\n color: undefined,\n size: 'md',\n removable: false,\n disabled: false,\n icon: false,\n})\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--${variant}`,\n color && `mint-pill--${color}`,\n `mint-pill--${size}`,\n { 'mint-pill--disabled': disabled, 'mint-pill--with-icon': icon },\n ]\"\n >\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\n<style>\n@import '../styles/components/pill.css';\n</style>\n","<script setup lang=\"ts\">\n/** Compact label for tags, status indicators, and badges; supports removable and icon slots. */\nimport type { PillVariant, PillColor, PillSize } from '../types'\n\n/**\n * BasePill - Compact label component for tags, status indicators, and badges.\n *\n * @example\n * ```vue\n * <BasePill variant=\"success\">Active</BasePill>\n * <BasePill variant=\"warning\" removable @remove=\"handleRemove\">Tag</BasePill>\n * <BasePill :icon=\"true\">\n * <template #icon><CheckIcon /></template>\n * Verified\n * </BasePill>\n * ```\n */\ninterface Props {\n /** Visual style variant */\n variant?: PillVariant\n /** Semantic color modifier — use with variant=\"outline\" for colored outlines */\n color?: PillColor\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}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'default',\n color: undefined,\n size: 'md',\n removable: false,\n disabled: false,\n icon: false,\n})\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--${variant}`,\n color && `mint-pill--${color}`,\n `mint-pill--${size}`,\n { 'mint-pill--disabled': disabled, 'mint-pill--with-icon': icon },\n ]\"\n >\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\n<style>\n@import '../styles/components/pill.css';\n</style>\n","<script setup lang=\"ts\">\n/** Animated loading placeholder in text, circular, rectangular, or rounded variants with pulse or wave animation. */\nimport { computed } from 'vue'\n\ninterface Props {\n variant?: 'text' | 'circular' | 'rectangular' | 'rounded'\n width?: string | number\n height?: string | number\n animation?: 'pulse' | 'wave' | 'none'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'text',\n animation: 'wave',\n})\n\nconst style = computed(() => {\n const s: Record<string, string> = {}\n\n if (props.width) {\n s.width = typeof props.width === 'number' ? `${props.width}px` : props.width\n }\n\n if (props.height) {\n s.height = typeof props.height === 'number' ? `${props.height}px` : props.height\n }\n\n return s\n})\n\nconst classes = computed(() => {\n const base = ['bg-bg-hover']\n\n switch (props.variant) {\n case 'circular':\n base.push('rounded-full')\n if (!props.width && !props.height) {\n base.push('w-10 h-10')\n }\n break\n case 'rectangular':\n base.push('rounded-none')\n if (!props.height) base.push('h-24')\n break\n case 'rounded':\n base.push('rounded-mint')\n if (!props.height) base.push('h-24')\n break\n default:\n base.push('rounded')\n if (!props.height) base.push('h-4')\n if (!props.width) base.push('w-full')\n }\n\n switch (props.animation) {\n case 'pulse':\n base.push('animate-pulse')\n break\n case 'wave':\n base.push('skeleton-wave')\n break\n }\n\n return base\n})\n</script>\n\n<template>\n <div :class=\"classes\" :style=\"style\" />\n</template>\n\n<style>\n@import '../styles/components/skeleton.css';\n</style>\n","<script setup lang=\"ts\">\n/** Animated loading placeholder in text, circular, rectangular, or rounded variants with pulse or wave animation. */\nimport { computed } from 'vue'\n\ninterface Props {\n variant?: 'text' | 'circular' | 'rectangular' | 'rounded'\n width?: string | number\n height?: string | number\n animation?: 'pulse' | 'wave' | 'none'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n variant: 'text',\n animation: 'wave',\n})\n\nconst style = computed(() => {\n const s: Record<string, string> = {}\n\n if (props.width) {\n s.width = typeof props.width === 'number' ? `${props.width}px` : props.width\n }\n\n if (props.height) {\n s.height = typeof props.height === 'number' ? `${props.height}px` : props.height\n }\n\n return s\n})\n\nconst classes = computed(() => {\n const base = ['bg-bg-hover']\n\n switch (props.variant) {\n case 'circular':\n base.push('rounded-full')\n if (!props.width && !props.height) {\n base.push('w-10 h-10')\n }\n break\n case 'rectangular':\n base.push('rounded-none')\n if (!props.height) base.push('h-24')\n break\n case 'rounded':\n base.push('rounded-mint')\n if (!props.height) base.push('h-24')\n break\n default:\n base.push('rounded')\n if (!props.height) base.push('h-4')\n if (!props.width) base.push('w-full')\n }\n\n switch (props.animation) {\n case 'pulse':\n base.push('animate-pulse')\n break\n case 'wave':\n base.push('skeleton-wave')\n break\n }\n\n return base\n})\n</script>\n\n<template>\n <div :class=\"classes\" :style=\"style\" />\n</template>\n\n<style>\n@import '../styles/components/skeleton.css';\n</style>\n","<script setup lang=\"ts\">\n/** Empty-state placeholder with icon badge, headline, description, default slot, and optional CTA button. */\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n title?: string\n description?: string\n iconPath?: string\n color?: 'primary' | 'cta' | 'success' | 'warning' | 'error' | 'muted'\n size?: 'sm' | 'md' | 'lg'\n variant?: 'illustrated' | 'inline'\n actionLabel?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n color: 'primary',\n size: 'md',\n variant: 'illustrated',\n})\n\nconst emit = defineEmits<{\n action: []\n}>()\n\nconst defaultIconPaths = [\n 'M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z',\n 'M7 11h10',\n 'M7 15h6',\n 'M7 7h8',\n]\n</script>\n\n<template>\n <div :class=\"['mint-empty-state', `mint-empty-state--${variant}`, `mint-empty-state--${size}`]\">\n <div :class=\"['mint-empty-state__icon-wrapper', `mint-empty-state__icon-wrapper--${color}`]\">\n <slot name=\"icon\">\n <svg\n class=\"mint-empty-state__icon\"\n :class=\"`mint-empty-state__icon--${color}`\"\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 <template v-if=\"iconPath\">\n <path :d=\"iconPath\" />\n </template>\n <template v-else>\n <path v-for=\"(d, i) in defaultIconPaths\" :key=\"i\" :d=\"d\" />\n </template>\n </svg>\n </slot>\n </div>\n <div class=\"mint-empty-state__body\">\n <h3 v-if=\"title\" class=\"mint-empty-state__title\">{{ title }}</h3>\n <p v-if=\"description\" class=\"mint-empty-state__description\">{{ description }}</p>\n <slot />\n </div>\n <div v-if=\"actionLabel\" class=\"mint-empty-state__action\">\n <BaseButton @click=\"emit('action')\">\n {{ actionLabel }}\n </BaseButton>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/empty-state.css';\n</style>\n","<script setup lang=\"ts\">\n/** Empty-state placeholder with icon badge, headline, description, default slot, and optional CTA button. */\nimport BaseButton from './BaseButton.vue'\n\ninterface Props {\n title?: string\n description?: string\n iconPath?: string\n color?: 'primary' | 'cta' | 'success' | 'warning' | 'error' | 'muted'\n size?: 'sm' | 'md' | 'lg'\n variant?: 'illustrated' | 'inline'\n actionLabel?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n color: 'primary',\n size: 'md',\n variant: 'illustrated',\n})\n\nconst emit = defineEmits<{\n action: []\n}>()\n\nconst defaultIconPaths = [\n 'M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z',\n 'M7 11h10',\n 'M7 15h6',\n 'M7 7h8',\n]\n</script>\n\n<template>\n <div :class=\"['mint-empty-state', `mint-empty-state--${variant}`, `mint-empty-state--${size}`]\">\n <div :class=\"['mint-empty-state__icon-wrapper', `mint-empty-state__icon-wrapper--${color}`]\">\n <slot name=\"icon\">\n <svg\n class=\"mint-empty-state__icon\"\n :class=\"`mint-empty-state__icon--${color}`\"\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 <template v-if=\"iconPath\">\n <path :d=\"iconPath\" />\n </template>\n <template v-else>\n <path v-for=\"(d, i) in defaultIconPaths\" :key=\"i\" :d=\"d\" />\n </template>\n </svg>\n </slot>\n </div>\n <div class=\"mint-empty-state__body\">\n <h3 v-if=\"title\" class=\"mint-empty-state__title\">{{ title }}</h3>\n <p v-if=\"description\" class=\"mint-empty-state__description\">{{ description }}</p>\n <slot />\n </div>\n <div v-if=\"actionLabel\" class=\"mint-empty-state__action\">\n <BaseButton @click=\"emit('action')\">\n {{ actionLabel }}\n </BaseButton>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/empty-state.css';\n</style>\n","<script setup lang=\"ts\">\n/** Inline badge that displays an experiment code and copies it to the clipboard on click. */\nimport { ref } from 'vue'\n\ninterface Props {\n code: string\n size?: 'sm' | 'md' | 'lg'\n copyable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: 'md',\n copyable: true,\n})\n\nconst emit = defineEmits<{\n copy: [code: string]\n}>()\n\nconst copied = ref(false)\nlet copyTimeout: ReturnType<typeof setTimeout> | null = null\n\nasync function handleCopy() {\n if (!props.copyable) return\n try {\n await navigator.clipboard.writeText(props.code)\n copied.value = true\n emit('copy', props.code)\n if (copyTimeout) clearTimeout(copyTimeout)\n copyTimeout = setTimeout(() => { copied.value = false }, 1500)\n } catch {\n // Clipboard API not available (e.g. insecure context)\n }\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n if (!props.copyable) return\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n handleCopy()\n }\n}\n</script>\n\n<template>\n <span\n :class=\"[\n 'mint-exp-code',\n `mint-exp-code--${size}`,\n { 'mint-exp-code--copyable': copyable, 'mint-exp-code--copied': copied },\n ]\"\n :role=\"copyable ? 'button' : undefined\"\n :tabindex=\"copyable ? 0 : undefined\"\n :title=\"copyable ? (copied ? 'Copied!' : 'Click to copy') : undefined\"\n @click=\"handleCopy\"\n @keydown=\"handleKeydown\"\n >\n {{ copied ? 'Copied!' : code }}\n </span>\n</template>\n\n<style>\n@import '../styles/components/experiment-code-badge.css';\n</style>\n","<script setup lang=\"ts\">\n/** Inline badge that displays an experiment code and copies it to the clipboard on click. */\nimport { ref } from 'vue'\n\ninterface Props {\n code: string\n size?: 'sm' | 'md' | 'lg'\n copyable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n size: 'md',\n copyable: true,\n})\n\nconst emit = defineEmits<{\n copy: [code: string]\n}>()\n\nconst copied = ref(false)\nlet copyTimeout: ReturnType<typeof setTimeout> | null = null\n\nasync function handleCopy() {\n if (!props.copyable) return\n try {\n await navigator.clipboard.writeText(props.code)\n copied.value = true\n emit('copy', props.code)\n if (copyTimeout) clearTimeout(copyTimeout)\n copyTimeout = setTimeout(() => { copied.value = false }, 1500)\n } catch {\n // Clipboard API not available (e.g. insecure context)\n }\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n if (!props.copyable) return\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n handleCopy()\n }\n}\n</script>\n\n<template>\n <span\n :class=\"[\n 'mint-exp-code',\n `mint-exp-code--${size}`,\n { 'mint-exp-code--copyable': copyable, 'mint-exp-code--copied': copied },\n ]\"\n :role=\"copyable ? 'button' : undefined\"\n :tabindex=\"copyable ? 0 : undefined\"\n :title=\"copyable ? (copied ? 'Copied!' : 'Click to copy') : undefined\"\n @click=\"handleCopy\"\n @keydown=\"handleKeydown\"\n >\n {{ copied ? 'Copied!' : code }}\n </span>\n</template>\n\n<style>\n@import '../styles/components/experiment-code-badge.css';\n</style>\n","<script setup lang=\"ts\">\n/** Modal for searching and selecting an experiment from the platform list with filters and keyboard nav. */\nimport { ref, reactive, computed, watch, nextTick } from 'vue'\nimport type { ModalSize, ExperimentSummary, ExperimentFilters } from '../types'\nimport { useExperimentSelector } from '../composables/useExperimentSelector'\nimport {\n formatExperimentDate,\n formatExperimentStatus,\n getExperimentStatusVariant,\n EXPERIMENT_STATUS_OPTIONS,\n DATE_PRESET_OPTIONS,\n SORT_OPTIONS,\n} from '../composables/experiment-utils'\nimport BaseModal from './BaseModal.vue'\nimport BaseInput from './BaseInput.vue'\nimport BaseSelect from './BaseSelect.vue'\nimport BasePill from './BasePill.vue'\nimport Skeleton from './Skeleton.vue'\nimport EmptyState from './EmptyState.vue'\nimport ExperimentCodeBadge from './ExperimentCodeBadge.vue'\n\ninterface Props {\n modelValue: boolean\n experimentType?: string\n allowedExperimentTypes?: string[] | null\n currentExperimentId?: number | null\n title?: string\n size?: ModalSize\n groupByProject?: boolean\n showFilters?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n currentExperimentId: null,\n title: 'Select Experiment',\n size: 'full',\n groupByProject: false,\n showFilters: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n select: [experiment: ExperimentSummary]\n deselect: []\n}>()\n\nconst {\n experiments,\n filters,\n isLoading,\n error,\n sortKey,\n experimentTypes,\n projects,\n groupedByProject,\n fetch: fetchExperiments,\n fetchFilterOptions,\n} = useExperimentSelector({\n experimentType: props.experimentType,\n allowedExperimentTypes: props.allowedExperimentTypes,\n})\n\nconst activeIndex = ref(-1)\nconst listRef = ref<HTMLElement | null>(null)\nconst showAdvanced = ref(props.showFilters)\nconst groupToggle = ref(props.groupByProject)\n\n// Track whether any advanced filter is active (for badge dot)\nconst hasActiveAdvancedFilters = computed(() =>\n !!(filters.project || filters.experimentType || filters.datePreset || sortKey.value !== 'created_at:desc'),\n)\n\n// Build type filter options from fetched experiment types\nconst typeFilterOptions = computed(() => [\n { value: '', label: 'All types' },\n ...experimentTypes.value.map(t => ({ value: t.value, label: t.label })),\n])\nconst canChooseExperimentType = computed(() =>\n !props.experimentType && typeFilterOptions.value.length > 2,\n)\n\n// Build project filter options from fetched projects\nconst projectFilterOptions = computed(() => [\n { value: '', label: 'All projects' },\n ...projects.value,\n])\n\n// Flat list of experiments for keyboard navigation (works across groups too)\nconst flatExperiments = computed(() => {\n if (!groupToggle.value) return experiments.value\n return groupedByProject.value.flatMap(([, exps]) => exps)\n})\n\nfunction setFilter<K extends keyof ExperimentFilters>(key: K, value: string | number) {\n ;(filters as Record<string, unknown>)[key] = String(value) || undefined\n}\n\nfunction handleSortChange(value: string | number) {\n sortKey.value = String(value) || 'created_at:desc'\n}\n\nfunction handleSelect(experiment: ExperimentSummary) {\n emit('select', experiment)\n emit('update:modelValue', false)\n}\n\nfunction handleDeselect() {\n emit('deselect')\n emit('update:modelValue', false)\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n const list = flatExperiments.value\n if (!list.length) return\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault()\n activeIndex.value = Math.min(activeIndex.value + 1, list.length - 1)\n scrollActiveIntoView()\n break\n case 'ArrowUp':\n event.preventDefault()\n activeIndex.value = Math.max(activeIndex.value - 1, 0)\n scrollActiveIntoView()\n break\n case 'Enter':\n event.preventDefault()\n if (activeIndex.value >= 0 && activeIndex.value < list.length) {\n handleSelect(list[activeIndex.value])\n }\n break\n }\n}\n\nfunction scrollActiveIntoView() {\n nextTick(() => {\n const row = listRef.value?.querySelector('.mint-experiment-selector__row--focused')\n row?.scrollIntoView({ block: 'nearest' })\n })\n}\n\n// Precomputed id → flat index for O(1) lookup in grouped mode\nconst flatIndexMap = computed(() => {\n const map = new Map<number, number>()\n flatExperiments.value.forEach((exp, i) => map.set(exp.id, i))\n return map\n})\n\nfunction getFlatIndex(experiment: ExperimentSummary): number {\n return flatIndexMap.value.get(experiment.id) ?? -1\n}\n\n// Track collapsed groups (reactive Set tracks .add/.delete/.has automatically)\nconst collapsedGroups = reactive(new Set<string>())\n\nfunction toggleGroup(groupName: string) {\n if (collapsedGroups.has(groupName)) {\n collapsedGroups.delete(groupName)\n } else {\n collapsedGroups.add(groupName)\n }\n}\n\n// Reset active index when experiments change\nwatch(experiments, () => { activeIndex.value = -1 })\n\n// Fetch on open\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (isOpen) {\n activeIndex.value = -1\n collapsedGroups.clear()\n fetchFilterOptions()\n fetchExperiments()\n }\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :size=\"size\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <div class=\"mint-experiment-selector\" @keydown=\"handleKeydown\">\n <!-- Filter bar row 1 -->\n <div class=\"mint-experiment-selector__filters-row\">\n <div class=\"mint-experiment-selector__search\">\n <BaseInput\n v-model=\"filters.search\"\n placeholder=\"Search experiments...\"\n size=\"sm\"\n type=\"search\"\n />\n </div>\n <div class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.status ?? ''\"\n :options=\"EXPERIMENT_STATUS_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('status', v)\"\n />\n </div>\n <div v-if=\"canChooseExperimentType\" class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.experimentType ?? ''\"\n :options=\"typeFilterOptions\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('experimentType', v)\"\n />\n </div>\n <button\n class=\"mint-experiment-selector__filters-toggle\"\n :class=\"{ 'mint-experiment-selector__filters-toggle--active': hasActiveAdvancedFilters }\"\n type=\"button\"\n @click=\"showAdvanced = !showAdvanced\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\" /><line x1=\"8\" y1=\"12\" x2=\"20\" y2=\"12\" /><line x1=\"12\" y1=\"18\" x2=\"20\" y2=\"18\" />\n <circle cx=\"6\" cy=\"12\" r=\"2\" /><circle cx=\"10\" cy=\"18\" r=\"2\" /><circle cx=\"6\" cy=\"6\" r=\"2\" />\n </svg>\n Filters\n <span v-if=\"hasActiveAdvancedFilters\" class=\"mint-experiment-selector__filters-dot\" />\n </button>\n </div>\n\n <!-- Filter bar row 2 (advanced, collapsible) -->\n <div v-if=\"showAdvanced\" class=\"mint-experiment-selector__filters-advanced\">\n <div v-if=\"projectFilterOptions.length > 1\" class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.project ?? ''\"\n :options=\"projectFilterOptions\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('project', v)\"\n />\n </div>\n <div class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.datePreset ?? ''\"\n :options=\"DATE_PRESET_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('datePreset', v)\"\n />\n </div>\n <div class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"sortKey\"\n :options=\"SORT_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"handleSortChange\"\n />\n </div>\n <label class=\"mint-experiment-selector__group-toggle\">\n <input\n v-model=\"groupToggle\"\n type=\"checkbox\"\n class=\"mint-experiment-selector__group-checkbox\"\n />\n Group by project\n </label>\n </div>\n\n <!-- Loading skeleton -->\n <div v-if=\"isLoading\" class=\"mint-experiment-selector__skeleton\">\n <div v-for=\"n in 4\" :key=\"n\" class=\"mint-experiment-selector__skeleton-row\">\n <div class=\"mint-experiment-selector__skeleton-content\">\n <Skeleton :width=\"120 + n * 20\" height=\"14px\" />\n <Skeleton width=\"80px\" height=\"10px\" />\n </div>\n <Skeleton width=\"60px\" height=\"20px\" variant=\"rounded\" />\n </div>\n </div>\n\n <!-- Error -->\n <div v-else-if=\"error\" class=\"mint-experiment-selector__error\">\n {{ error }}\n </div>\n\n <!-- Empty -->\n <EmptyState\n v-else-if=\"experiments.length === 0\"\n title=\"No experiments found\"\n description=\"Try adjusting your search or filters.\"\n size=\"sm\"\n />\n\n <!-- Experiment list: grouped mode -->\n <div v-else-if=\"groupToggle\" ref=\"listRef\" class=\"mint-experiment-selector__list\">\n <template v-for=\"([groupName, groupExps]) in groupedByProject\" :key=\"groupName\">\n <button\n type=\"button\"\n class=\"mint-experiment-selector__group-header\"\n @click=\"toggleGroup(groupName)\"\n >\n <svg\n class=\"mint-experiment-selector__group-chevron\"\n :class=\"{ 'mint-experiment-selector__group-chevron--collapsed': collapsedGroups.has(groupName) }\"\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n <span class=\"mint-experiment-selector__group-name\">{{ groupName }}</span>\n <span class=\"mint-experiment-selector__group-count\">{{ groupExps.length }}</span>\n </button>\n <template v-if=\"!collapsedGroups.has(groupName)\">\n <div\n v-for=\"exp in groupExps\"\n :key=\"exp.id\"\n class=\"mint-experiment-selector__row\"\n :class=\"{\n 'mint-experiment-selector__row--active': exp.id === currentExperimentId,\n 'mint-experiment-selector__row--focused': getFlatIndex(exp) === activeIndex,\n }\"\n @click=\"handleSelect(exp)\"\n @mouseenter=\"activeIndex = getFlatIndex(exp)\"\n >\n <div class=\"mint-experiment-selector__row-content\">\n <div class=\"mint-experiment-selector__name\">\n {{ exp.name }}\n <ExperimentCodeBadge\n v-if=\"exp.experiment_code\"\n :code=\"exp.experiment_code\"\n size=\"sm\"\n :copyable=\"false\"\n />\n </div>\n <div class=\"mint-experiment-selector__meta\">\n <span>{{ formatExperimentDate(exp.created_at) }}</span>\n </div>\n </div>\n <BasePill :variant=\"getExperimentStatusVariant(exp.status)\" size=\"sm\">\n {{ formatExperimentStatus(exp.status) }}\n </BasePill>\n </div>\n </template>\n </template>\n </div>\n\n <!-- Experiment list: flat mode -->\n <div v-else ref=\"listRef\" class=\"mint-experiment-selector__list\">\n <div\n v-for=\"(exp, idx) in experiments\"\n :key=\"exp.id\"\n class=\"mint-experiment-selector__row\"\n :class=\"{\n 'mint-experiment-selector__row--active': exp.id === currentExperimentId,\n 'mint-experiment-selector__row--focused': idx === activeIndex,\n }\"\n @click=\"handleSelect(exp)\"\n @mouseenter=\"activeIndex = idx\"\n >\n <div class=\"mint-experiment-selector__row-content\">\n <div class=\"mint-experiment-selector__name\">\n {{ exp.name }}\n <ExperimentCodeBadge\n v-if=\"exp.experiment_code\"\n :code=\"exp.experiment_code\"\n size=\"sm\"\n :copyable=\"false\"\n />\n </div>\n <div class=\"mint-experiment-selector__meta\">\n <span v-if=\"exp.project_name || exp.project\">{{ exp.project_name || exp.project }}</span>\n <span>{{ formatExperimentDate(exp.created_at) }}</span>\n </div>\n </div>\n <BasePill :variant=\"getExperimentStatusVariant(exp.status)\" size=\"sm\">\n {{ formatExperimentStatus(exp.status) }}\n </BasePill>\n </div>\n </div>\n\n <!-- Footer: clear selection -->\n <div v-if=\"currentExperimentId != null\" class=\"mint-experiment-selector__footer\">\n <button\n type=\"button\"\n class=\"mint-experiment-selector__clear-btn\"\n @click=\"handleDeselect\"\n >\n Clear selection\n </button>\n </div>\n </div>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/experiment-selector-modal.css';\n</style>\n","<script setup lang=\"ts\">\n/** Modal for searching and selecting an experiment from the platform list with filters and keyboard nav. */\nimport { ref, reactive, computed, watch, nextTick } from 'vue'\nimport type { ModalSize, ExperimentSummary, ExperimentFilters } from '../types'\nimport { useExperimentSelector } from '../composables/useExperimentSelector'\nimport {\n formatExperimentDate,\n formatExperimentStatus,\n getExperimentStatusVariant,\n EXPERIMENT_STATUS_OPTIONS,\n DATE_PRESET_OPTIONS,\n SORT_OPTIONS,\n} from '../composables/experiment-utils'\nimport BaseModal from './BaseModal.vue'\nimport BaseInput from './BaseInput.vue'\nimport BaseSelect from './BaseSelect.vue'\nimport BasePill from './BasePill.vue'\nimport Skeleton from './Skeleton.vue'\nimport EmptyState from './EmptyState.vue'\nimport ExperimentCodeBadge from './ExperimentCodeBadge.vue'\n\ninterface Props {\n modelValue: boolean\n experimentType?: string\n allowedExperimentTypes?: string[] | null\n currentExperimentId?: number | null\n title?: string\n size?: ModalSize\n groupByProject?: boolean\n showFilters?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n currentExperimentId: null,\n title: 'Select Experiment',\n size: 'full',\n groupByProject: false,\n showFilters: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n select: [experiment: ExperimentSummary]\n deselect: []\n}>()\n\nconst {\n experiments,\n filters,\n isLoading,\n error,\n sortKey,\n experimentTypes,\n projects,\n groupedByProject,\n fetch: fetchExperiments,\n fetchFilterOptions,\n} = useExperimentSelector({\n experimentType: props.experimentType,\n allowedExperimentTypes: props.allowedExperimentTypes,\n})\n\nconst activeIndex = ref(-1)\nconst listRef = ref<HTMLElement | null>(null)\nconst showAdvanced = ref(props.showFilters)\nconst groupToggle = ref(props.groupByProject)\n\n// Track whether any advanced filter is active (for badge dot)\nconst hasActiveAdvancedFilters = computed(() =>\n !!(filters.project || filters.experimentType || filters.datePreset || sortKey.value !== 'created_at:desc'),\n)\n\n// Build type filter options from fetched experiment types\nconst typeFilterOptions = computed(() => [\n { value: '', label: 'All types' },\n ...experimentTypes.value.map(t => ({ value: t.value, label: t.label })),\n])\nconst canChooseExperimentType = computed(() =>\n !props.experimentType && typeFilterOptions.value.length > 2,\n)\n\n// Build project filter options from fetched projects\nconst projectFilterOptions = computed(() => [\n { value: '', label: 'All projects' },\n ...projects.value,\n])\n\n// Flat list of experiments for keyboard navigation (works across groups too)\nconst flatExperiments = computed(() => {\n if (!groupToggle.value) return experiments.value\n return groupedByProject.value.flatMap(([, exps]) => exps)\n})\n\nfunction setFilter<K extends keyof ExperimentFilters>(key: K, value: string | number) {\n ;(filters as Record<string, unknown>)[key] = String(value) || undefined\n}\n\nfunction handleSortChange(value: string | number) {\n sortKey.value = String(value) || 'created_at:desc'\n}\n\nfunction handleSelect(experiment: ExperimentSummary) {\n emit('select', experiment)\n emit('update:modelValue', false)\n}\n\nfunction handleDeselect() {\n emit('deselect')\n emit('update:modelValue', false)\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n const list = flatExperiments.value\n if (!list.length) return\n\n switch (event.key) {\n case 'ArrowDown':\n event.preventDefault()\n activeIndex.value = Math.min(activeIndex.value + 1, list.length - 1)\n scrollActiveIntoView()\n break\n case 'ArrowUp':\n event.preventDefault()\n activeIndex.value = Math.max(activeIndex.value - 1, 0)\n scrollActiveIntoView()\n break\n case 'Enter':\n event.preventDefault()\n if (activeIndex.value >= 0 && activeIndex.value < list.length) {\n handleSelect(list[activeIndex.value])\n }\n break\n }\n}\n\nfunction scrollActiveIntoView() {\n nextTick(() => {\n const row = listRef.value?.querySelector('.mint-experiment-selector__row--focused')\n row?.scrollIntoView({ block: 'nearest' })\n })\n}\n\n// Precomputed id → flat index for O(1) lookup in grouped mode\nconst flatIndexMap = computed(() => {\n const map = new Map<number, number>()\n flatExperiments.value.forEach((exp, i) => map.set(exp.id, i))\n return map\n})\n\nfunction getFlatIndex(experiment: ExperimentSummary): number {\n return flatIndexMap.value.get(experiment.id) ?? -1\n}\n\n// Track collapsed groups (reactive Set tracks .add/.delete/.has automatically)\nconst collapsedGroups = reactive(new Set<string>())\n\nfunction toggleGroup(groupName: string) {\n if (collapsedGroups.has(groupName)) {\n collapsedGroups.delete(groupName)\n } else {\n collapsedGroups.add(groupName)\n }\n}\n\n// Reset active index when experiments change\nwatch(experiments, () => { activeIndex.value = -1 })\n\n// Fetch on open\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (isOpen) {\n activeIndex.value = -1\n collapsedGroups.clear()\n fetchFilterOptions()\n fetchExperiments()\n }\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <BaseModal\n :model-value=\"modelValue\"\n :title=\"title\"\n :size=\"size\"\n @update:model-value=\"emit('update:modelValue', $event)\"\n >\n <div class=\"mint-experiment-selector\" @keydown=\"handleKeydown\">\n <!-- Filter bar row 1 -->\n <div class=\"mint-experiment-selector__filters-row\">\n <div class=\"mint-experiment-selector__search\">\n <BaseInput\n v-model=\"filters.search\"\n placeholder=\"Search experiments...\"\n size=\"sm\"\n type=\"search\"\n />\n </div>\n <div class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.status ?? ''\"\n :options=\"EXPERIMENT_STATUS_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('status', v)\"\n />\n </div>\n <div v-if=\"canChooseExperimentType\" class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.experimentType ?? ''\"\n :options=\"typeFilterOptions\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('experimentType', v)\"\n />\n </div>\n <button\n class=\"mint-experiment-selector__filters-toggle\"\n :class=\"{ 'mint-experiment-selector__filters-toggle--active': hasActiveAdvancedFilters }\"\n type=\"button\"\n @click=\"showAdvanced = !showAdvanced\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\" /><line x1=\"8\" y1=\"12\" x2=\"20\" y2=\"12\" /><line x1=\"12\" y1=\"18\" x2=\"20\" y2=\"18\" />\n <circle cx=\"6\" cy=\"12\" r=\"2\" /><circle cx=\"10\" cy=\"18\" r=\"2\" /><circle cx=\"6\" cy=\"6\" r=\"2\" />\n </svg>\n Filters\n <span v-if=\"hasActiveAdvancedFilters\" class=\"mint-experiment-selector__filters-dot\" />\n </button>\n </div>\n\n <!-- Filter bar row 2 (advanced, collapsible) -->\n <div v-if=\"showAdvanced\" class=\"mint-experiment-selector__filters-advanced\">\n <div v-if=\"projectFilterOptions.length > 1\" class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.project ?? ''\"\n :options=\"projectFilterOptions\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('project', v)\"\n />\n </div>\n <div class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"filters.datePreset ?? ''\"\n :options=\"DATE_PRESET_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"v => setFilter('datePreset', v)\"\n />\n </div>\n <div class=\"mint-experiment-selector__filter-select\">\n <BaseSelect\n :model-value=\"sortKey\"\n :options=\"SORT_OPTIONS\"\n size=\"sm\"\n @update:model-value=\"handleSortChange\"\n />\n </div>\n <label class=\"mint-experiment-selector__group-toggle\">\n <input\n v-model=\"groupToggle\"\n type=\"checkbox\"\n class=\"mint-experiment-selector__group-checkbox\"\n />\n Group by project\n </label>\n </div>\n\n <!-- Loading skeleton -->\n <div v-if=\"isLoading\" class=\"mint-experiment-selector__skeleton\">\n <div v-for=\"n in 4\" :key=\"n\" class=\"mint-experiment-selector__skeleton-row\">\n <div class=\"mint-experiment-selector__skeleton-content\">\n <Skeleton :width=\"120 + n * 20\" height=\"14px\" />\n <Skeleton width=\"80px\" height=\"10px\" />\n </div>\n <Skeleton width=\"60px\" height=\"20px\" variant=\"rounded\" />\n </div>\n </div>\n\n <!-- Error -->\n <div v-else-if=\"error\" class=\"mint-experiment-selector__error\">\n {{ error }}\n </div>\n\n <!-- Empty -->\n <EmptyState\n v-else-if=\"experiments.length === 0\"\n title=\"No experiments found\"\n description=\"Try adjusting your search or filters.\"\n size=\"sm\"\n />\n\n <!-- Experiment list: grouped mode -->\n <div v-else-if=\"groupToggle\" ref=\"listRef\" class=\"mint-experiment-selector__list\">\n <template v-for=\"([groupName, groupExps]) in groupedByProject\" :key=\"groupName\">\n <button\n type=\"button\"\n class=\"mint-experiment-selector__group-header\"\n @click=\"toggleGroup(groupName)\"\n >\n <svg\n class=\"mint-experiment-selector__group-chevron\"\n :class=\"{ 'mint-experiment-selector__group-chevron--collapsed': collapsedGroups.has(groupName) }\"\n width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n >\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n <span class=\"mint-experiment-selector__group-name\">{{ groupName }}</span>\n <span class=\"mint-experiment-selector__group-count\">{{ groupExps.length }}</span>\n </button>\n <template v-if=\"!collapsedGroups.has(groupName)\">\n <div\n v-for=\"exp in groupExps\"\n :key=\"exp.id\"\n class=\"mint-experiment-selector__row\"\n :class=\"{\n 'mint-experiment-selector__row--active': exp.id === currentExperimentId,\n 'mint-experiment-selector__row--focused': getFlatIndex(exp) === activeIndex,\n }\"\n @click=\"handleSelect(exp)\"\n @mouseenter=\"activeIndex = getFlatIndex(exp)\"\n >\n <div class=\"mint-experiment-selector__row-content\">\n <div class=\"mint-experiment-selector__name\">\n {{ exp.name }}\n <ExperimentCodeBadge\n v-if=\"exp.experiment_code\"\n :code=\"exp.experiment_code\"\n size=\"sm\"\n :copyable=\"false\"\n />\n </div>\n <div class=\"mint-experiment-selector__meta\">\n <span>{{ formatExperimentDate(exp.created_at) }}</span>\n </div>\n </div>\n <BasePill :variant=\"getExperimentStatusVariant(exp.status)\" size=\"sm\">\n {{ formatExperimentStatus(exp.status) }}\n </BasePill>\n </div>\n </template>\n </template>\n </div>\n\n <!-- Experiment list: flat mode -->\n <div v-else ref=\"listRef\" class=\"mint-experiment-selector__list\">\n <div\n v-for=\"(exp, idx) in experiments\"\n :key=\"exp.id\"\n class=\"mint-experiment-selector__row\"\n :class=\"{\n 'mint-experiment-selector__row--active': exp.id === currentExperimentId,\n 'mint-experiment-selector__row--focused': idx === activeIndex,\n }\"\n @click=\"handleSelect(exp)\"\n @mouseenter=\"activeIndex = idx\"\n >\n <div class=\"mint-experiment-selector__row-content\">\n <div class=\"mint-experiment-selector__name\">\n {{ exp.name }}\n <ExperimentCodeBadge\n v-if=\"exp.experiment_code\"\n :code=\"exp.experiment_code\"\n size=\"sm\"\n :copyable=\"false\"\n />\n </div>\n <div class=\"mint-experiment-selector__meta\">\n <span v-if=\"exp.project_name || exp.project\">{{ exp.project_name || exp.project }}</span>\n <span>{{ formatExperimentDate(exp.created_at) }}</span>\n </div>\n </div>\n <BasePill :variant=\"getExperimentStatusVariant(exp.status)\" size=\"sm\">\n {{ formatExperimentStatus(exp.status) }}\n </BasePill>\n </div>\n </div>\n\n <!-- Footer: clear selection -->\n <div v-if=\"currentExperimentId != null\" class=\"mint-experiment-selector__footer\">\n <button\n type=\"button\"\n class=\"mint-experiment-selector__clear-btn\"\n @click=\"handleDeselect\"\n >\n Clear selection\n </button>\n </div>\n </div>\n </BaseModal>\n</template>\n\n<style>\n@import '../styles/components/experiment-selector-modal.css';\n</style>\n"],"mappings":"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"}