@morscherlab/mint-sdk 1.3.0-beta.1 → 1.3.0-beta.11

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 (136) hide show
  1. package/README.md +23 -140
  2. package/dist/{ResourcePickerListInternal-c3IT8PIu.js → ResourcePickerListInternal-CFAw4vTZ.js} +26 -19
  3. package/dist/ResourcePickerListInternal-CFAw4vTZ.js.map +1 -0
  4. package/dist/components/FilePicker.vue.d.ts +4 -1
  5. package/dist/components/SettingsModal.schema.d.ts +1 -1
  6. package/dist/components/SettingsModal.vue.d.ts +2 -0
  7. package/dist/components/WellPlate.colors.d.ts +1 -5
  8. package/dist/components/WellPlate.rendering.d.ts +5 -2
  9. package/dist/components/index.d.ts +32 -17
  10. package/dist/components/index.js +3 -3
  11. package/dist/components/internal/FilePickerListInternal.vue.d.ts +2 -0
  12. package/dist/components/internal/ResourcePickerListInternal.vue.d.ts +9 -3
  13. package/dist/{components-CvY105kJ.js → components-vHfxV2PB.js} +425 -392
  14. package/dist/components-vHfxV2PB.js.map +1 -0
  15. package/dist/composables/createFilePickerAdapter.d.ts +5 -1
  16. package/dist/composables/filePicker/usePickerNavigation.d.ts +3 -2
  17. package/dist/composables/index.d.ts +0 -3
  18. package/dist/composables/index.js +4 -4
  19. package/dist/composables/useAutoGroup.d.ts +1 -1
  20. package/dist/composables/useBioTemplateComponents.d.ts +15 -3
  21. package/dist/composables/useBioTemplateControls.d.ts +5 -1
  22. package/dist/composables/useBioTemplatePackWorkspace.d.ts +2 -0
  23. package/dist/composables/useBioTemplatePresetWorkspace.d.ts +5 -1
  24. package/dist/composables/useBioTemplateWorkspace.d.ts +5 -1
  25. package/dist/composables/useExperimentSave.d.ts +5 -1
  26. package/dist/composables/useFilePicker.d.ts +4 -1
  27. package/dist/composables/useGroupAssignment.d.ts +5 -1
  28. package/dist/composables/usePlatformContext.d.ts +3 -0
  29. package/dist/composables/useTemplateCollection.d.ts +5 -1
  30. package/dist/composables-D6uqp0UF.js +591 -0
  31. package/dist/composables-D6uqp0UF.js.map +1 -0
  32. package/dist/index.js +24 -24
  33. package/dist/index.js.map +1 -1
  34. package/dist/install.js +1 -1
  35. package/dist/lcms.d.ts +11 -11
  36. package/dist/styles.css +228 -214
  37. package/dist/templates/index.js +1 -1
  38. package/dist/{templates-Bj9hZ7iV.js → templates-BU1pet28.js} +31 -44
  39. package/dist/templates-BU1pet28.js.map +1 -0
  40. package/dist/types/auth.d.ts +0 -27
  41. package/dist/types/components.d.ts +8 -0
  42. package/dist/types/fileBrowserTypes.d.ts +13 -0
  43. package/dist/types/filePicker.d.ts +8 -1
  44. package/dist/types/index.d.ts +2 -2
  45. package/dist/types/platform.d.ts +2 -0
  46. package/dist/utils/rack.d.ts +16 -16
  47. package/dist/{validation-B4Y8_eEe.js → validation-Dmp9Q5W0.js} +50 -12
  48. package/dist/validation-Dmp9Q5W0.js.map +1 -0
  49. package/package.json +1 -6
  50. package/src/__tests__/components/ActionMenu.test.ts +10 -0
  51. package/src/__tests__/components/AppTopBar.test.ts +24 -8
  52. package/src/__tests__/components/AppTopBarPageSelector.test.ts +4 -4
  53. package/src/__tests__/components/EmptyState.test.ts +15 -0
  54. package/src/__tests__/components/ExperimentSelectorModal.test.ts +12 -5
  55. package/src/__tests__/components/FilePicker.test.ts +65 -1
  56. package/src/__tests__/components/FileUploader.test.ts +6 -9
  57. package/src/__tests__/components/ResourcePickerListInternal.test.ts +23 -4
  58. package/src/__tests__/components/SettingsModal.test.ts +22 -0
  59. package/src/__tests__/components/WellPlate.decoration.test.ts +4 -28
  60. package/src/__tests__/components/WellPlate.rendering.test.ts +20 -33
  61. package/src/__tests__/components/WellPlate.sampleTypeIndicator.test.ts +7 -7
  62. package/src/__tests__/composables/createFilePickerAdapter.test.ts +92 -1
  63. package/src/__tests__/composables/usePickerNavigation.test.ts +61 -1
  64. package/src/__tests__/composables/usePlatformFilePickerAdapter.test.ts +18 -6
  65. package/src/__tests__/docs/frontendDocsCatalog.test.ts +2 -2
  66. package/src/__tests__/styles/controlRefreshRules.test.ts +21 -8
  67. package/src/__tests__/styles/menusShellRefresh.test.ts +20 -5
  68. package/src/__tests__/templates/templatesDeprecation.test.ts +58 -11
  69. package/src/components/ActionMenu.vue +10 -2
  70. package/src/components/AppTopBar.vue +6 -4
  71. package/src/components/EmptyState.vue +3 -1
  72. package/src/components/ExperimentSelectorModal.vue +4 -1
  73. package/src/components/FilePicker.vue +42 -0
  74. package/src/components/FileUploader.vue +30 -46
  75. package/src/components/SettingsModal.schema.ts +4 -2
  76. package/src/components/SettingsModal.vue +4 -2
  77. package/src/components/WellPlate.colors.ts +6 -10
  78. package/src/components/WellPlate.legend.ts +4 -4
  79. package/src/components/WellPlate.rendering.ts +9 -8
  80. package/src/components/WellPlate.vue +5 -22
  81. package/src/components/index.ts +32 -17
  82. package/src/components/internal/AppTopBarPageSelectorInternal.vue +3 -6
  83. package/src/components/internal/FilePickerListInternal.vue +11 -2
  84. package/src/components/internal/ResourcePickerListInternal.vue +21 -27
  85. package/src/composables/autoGroup/index.ts +1 -1
  86. package/src/composables/createFilePickerAdapter.ts +82 -22
  87. package/src/composables/filePicker/usePickerNavigation.ts +29 -2
  88. package/src/composables/index.ts +0 -7
  89. package/src/composables/useAutoGroup.ts +1 -1
  90. package/src/composables/useBioTemplateComponents.ts +15 -3
  91. package/src/composables/useBioTemplateControls.ts +5 -1
  92. package/src/composables/useBioTemplatePackWorkspace.ts +2 -0
  93. package/src/composables/useBioTemplatePresetWorkspace.ts +5 -1
  94. package/src/composables/useBioTemplateWorkspace.ts +5 -1
  95. package/src/composables/useExperimentSave.ts +5 -1
  96. package/src/composables/useFilePicker.ts +8 -1
  97. package/src/composables/useGroupAssignment.ts +5 -1
  98. package/src/composables/usePlatformFilePickerAdapter.ts +26 -4
  99. package/src/composables/useTemplateCollection.ts +5 -1
  100. package/src/index.ts +1 -1
  101. package/src/lcms.ts +11 -11
  102. package/src/styles/components/action-menu.css +10 -0
  103. package/src/styles/components/app-page-selector.css +61 -17
  104. package/src/styles/components/app-pill-nav.css +1 -1
  105. package/src/styles/components/app-top-bar.css +18 -38
  106. package/src/styles/components/empty-state.css +3 -2
  107. package/src/styles/components/experiment-selector-modal.css +3 -9
  108. package/src/styles/components/file-picker.css +23 -1
  109. package/src/styles/components/file-uploader.css +22 -55
  110. package/src/styles/components/resource-picker-list.css +70 -36
  111. package/src/styles/components/well-plate.css +18 -55
  112. package/src/types/auth.ts +0 -37
  113. package/src/types/components.ts +8 -0
  114. package/src/types/fileBrowserTypes.ts +15 -0
  115. package/src/types/filePicker.ts +8 -1
  116. package/src/types/index.ts +2 -5
  117. package/src/types/platform.ts +2 -0
  118. package/src/utils/rack.ts +16 -16
  119. package/dist/ResourcePickerListInternal-c3IT8PIu.js.map +0 -1
  120. package/dist/__tests__/composables/useAuth.test.d.ts +0 -1
  121. package/dist/__tests__/composables/usePasskey.test.d.ts +0 -1
  122. package/dist/components-CvY105kJ.js.map +0 -1
  123. package/dist/composables/useAuth.d.ts +0 -47
  124. package/dist/composables/usePasskey.d.ts +0 -10
  125. package/dist/composables/useWellPlateEditor.d.ts +0 -33
  126. package/dist/composables-DL4Vca7L.js +0 -1193
  127. package/dist/composables-DL4Vca7L.js.map +0 -1
  128. package/dist/plotly.min-D6Ge6qgX.js +0 -168582
  129. package/dist/plotly.min-D6Ge6qgX.js.map +0 -1
  130. package/dist/templates-Bj9hZ7iV.js.map +0 -1
  131. package/dist/validation-B4Y8_eEe.js.map +0 -1
  132. package/src/__tests__/composables/useAuth.test.ts +0 -290
  133. package/src/__tests__/composables/usePasskey.test.ts +0 -38
  134. package/src/composables/useAuth.ts +0 -470
  135. package/src/composables/usePasskey.ts +0 -216
  136. package/src/composables/useWellPlateEditor.ts +0 -415
@@ -31,7 +31,6 @@ const emit = defineEmits<{
31
31
  }>()
32
32
 
33
33
  const isDragOver = ref(false)
34
- const dragCount = ref(0)
35
34
  const inputRef = ref<HTMLInputElement>()
36
35
  const selectedFiles = ref<File[]>([])
37
36
  const folderName = ref<string | null>(null)
@@ -45,14 +44,9 @@ watch(() => [props.files, props.rootName] as const, ([files, rootName]) => {
45
44
  const isFolder = computed(() => props.mode === 'folder')
46
45
  const isMixed = computed(() => props.mode === 'file+folder')
47
46
 
48
- const promptText = computed(() => {
49
- if (isDragOver.value) {
50
- if (isFolder.value || dragCount.value === 0) return 'Release to add'
51
- return `Release to add ${dragCount.value} file${dragCount.value === 1 ? '' : 's'}`
52
- }
53
- if (isMixed.value) return 'Drop files or a folder, or click to choose files'
54
- if (isFolder.value) return 'Drop a folder, or click to choose'
55
- return props.multiple ? 'Drop files, or click to choose' : 'Drop a file, or click to choose'
47
+ const acceptLabel = computed(() => {
48
+ if (!props.accept) return isFolder.value ? 'a folder' : 'any file'
49
+ return props.accept.split(',').map(a => a.trim().replace('.', '').toUpperCase()).join(', ')
56
50
  })
57
51
 
58
52
  const maxSizeLabel = computed(() => {
@@ -69,12 +63,6 @@ const maxSizeLabel = computed(() => {
69
63
  return `${props.maxSize} bytes`
70
64
  })
71
65
 
72
- /** Accepted formats exactly as the caller wrote them, plus the size cap. */
73
- const formatsLabel = computed(() => [
74
- props.accept?.split(',').map(a => a.trim()).filter(Boolean).join(' '),
75
- maxSizeLabel.value && `max ${maxSizeLabel.value}`,
76
- ].filter(Boolean).join(' · '))
77
-
78
66
  const fileTypeMap: Record<string, { color: string; label: string }> = {
79
67
  csv: { color: '#10B981', label: 'CSV' },
80
68
  tsv: { color: '#10B981', label: 'TSV' },
@@ -272,7 +260,6 @@ function handleDragOver(event: DragEvent) {
272
260
  event.preventDefault()
273
261
  if (!props.disabled) {
274
262
  isDragOver.value = true
275
- dragCount.value = event.dataTransfer?.items?.length ?? 0
276
263
  }
277
264
  }
278
265
 
@@ -359,37 +346,34 @@ function formatFileSize(bytes: number): string {
359
346
  />
360
347
 
361
348
  <div class="mint-file-uploader__content">
362
- <span class="mint-file-uploader__icon-tile">
363
- <svg
364
- v-if="isFolder"
365
- class="mint-file-uploader__icon"
366
- fill="none"
367
- stroke="currentColor"
368
- stroke-width="1.5"
369
- stroke-linecap="round"
370
- stroke-linejoin="round"
371
- viewBox="0 0 24 24"
372
- >
373
- <path 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" />
374
- </svg>
375
- <svg
376
- v-else
377
- class="mint-file-uploader__icon"
378
- fill="none"
379
- stroke="currentColor"
380
- stroke-width="1.5"
381
- stroke-linecap="round"
382
- stroke-linejoin="round"
383
- viewBox="0 0 24 24"
384
- >
385
- <path d="M12 16V4M7 9l5-5 5 5M4 20h16" />
386
- </svg>
387
- </span>
349
+ <svg
350
+ v-if="isFolder"
351
+ class="mint-file-uploader__icon"
352
+ fill="none"
353
+ stroke="currentColor"
354
+ stroke-width="1.5"
355
+ stroke-linecap="round"
356
+ stroke-linejoin="round"
357
+ viewBox="0 0 24 24"
358
+ >
359
+ <path 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" />
360
+ </svg>
361
+ <svg
362
+ v-else
363
+ class="mint-file-uploader__icon"
364
+ fill="none"
365
+ stroke="currentColor"
366
+ stroke-width="1.5"
367
+ stroke-linecap="round"
368
+ stroke-linejoin="round"
369
+ viewBox="0 0 24 24"
370
+ >
371
+ <path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3" /><path d="m16 16-4-4-4 4" /><path d="M12 12v9" />
372
+ </svg>
388
373
 
389
- <div class="mint-file-uploader__copy">
390
- <p class="mint-file-uploader__text">{{ promptText }}</p>
391
- <p v-if="formatsLabel" class="mint-file-uploader__formats">{{ formatsLabel }}</p>
392
- </div>
374
+ <p class="mint-file-uploader__text">
375
+ {{ isMixed ? 'Drop files or a folder, or click to choose files' : `Drop ${acceptLabel} or click` }}
376
+ </p>
393
377
  </div>
394
378
  </div>
395
379
 
@@ -68,7 +68,9 @@ export function normalizeVisibleSettingsTabs(
68
68
 
69
69
  export function buildSettingsTabs(
70
70
  baseTabs: SettingsTab[],
71
- showAppearance: boolean
71
+ showAppearance: boolean,
72
+ appearanceFirst = false
72
73
  ): SettingsTab[] {
73
- return showAppearance ? [...baseTabs, APPEARANCE_TAB] : baseTabs
74
+ if (!showAppearance) return baseTabs
75
+ return appearanceFirst ? [APPEARANCE_TAB, ...baseTabs] : [...baseTabs, APPEARANCE_TAB]
74
76
  }
@@ -90,6 +90,8 @@ interface Props {
90
90
  /** Manual tab descriptors. Ignored when `schema` is set (groups become tabs). */
91
91
  tabs?: SettingsTabInput[]
92
92
  showAppearance?: boolean
93
+ /** List Appearance before the other tabs and open on it. */
94
+ appearanceFirst?: boolean
93
95
  size?: 'md' | 'lg' | 'xl'
94
96
  layout?: SettingsModalLayout
95
97
  /** Declarative schema — fields auto-render via SDK form components. */
@@ -238,7 +240,7 @@ const allTabs = computed<SettingsTab[]>(() => {
238
240
  const base: SettingsTab[] = settingsSchema.value
239
241
  ? visibleSchemaGroups.value.map(settingsGroupToTab)
240
242
  : manualTabs.value
241
- return buildSettingsTabs(base, props.showAppearance)
243
+ return buildSettingsTabs(base, props.showAppearance, props.appearanceFirst)
242
244
  })
243
245
 
244
246
  const activeTab = ref(allTabs.value[0]?.id || APPEARANCE_TAB_ID)
@@ -272,7 +274,7 @@ const modalTabs = computed<ModalTab[]>(() =>
272
274
  description: tab.description,
273
275
  meta: group ? (modified ? String(modified) : undefined) : tab.meta,
274
276
  // Sections only split plugin groups from Appearance; without it there is nothing to split.
275
- section: props.showAppearance ? (tab.id === APPEARANCE_TAB_ID ? 'Personal' : 'Plugin') : undefined,
277
+ section: props.showAppearance ? (tab.id === APPEARANCE_TAB_ID ? 'Personal' : tab.section ?? 'Plugin') : undefined,
276
278
  }
277
279
  }),
278
280
  )
@@ -1,16 +1,12 @@
1
1
  import type { HeatmapConfig } from '../types'
2
2
 
3
- /**
4
- * Group color for the built-in sample types when `sampleColors` has no entry.
5
- * Color encodes the group and shape encodes the type (3a): blank and QC render
6
- * neutral markers and ignore it; iQC takes it like a sample.
7
- */
3
+ /** Color for the built-in sample types when `sampleColors` has no entry. */
8
4
  export const DEFAULT_SAMPLE_TYPE_COLORS: Record<string, string> = {
9
- sample: 'var(--mint-sample-1)',
10
- control: 'var(--mint-sample-2)',
11
- blank: 'var(--mint-sample-1)',
12
- qc: 'var(--mint-sample-1)',
13
- iqc: 'var(--mint-sample-1)',
5
+ sample: '#10b981',
6
+ control: '#3b82f6',
7
+ blank: '#f97316',
8
+ qc: '#8b5cf6',
9
+ iqc: '#ec4899',
14
10
  }
15
11
 
16
12
  export const HEATMAP_COLORS: Record<string, string[]> = {
@@ -1,8 +1,8 @@
1
1
  import type { WellLegendItem } from '../types'
2
2
 
3
3
  export const DEFAULT_WELL_LEGEND_ITEMS: WellLegendItem[] = [
4
- { type: 'sample', label: 'Sample', color: 'var(--mint-sample-1)' },
5
- { type: 'blank', label: 'Blank', color: 'var(--mint-sample-1)' },
6
- { type: 'qc', label: 'QC', color: 'var(--mint-sample-1)' },
7
- { type: 'iqc', label: 'IQC', color: 'var(--mint-sample-1)' },
4
+ { type: 'sample', label: 'Sample', color: '#10b981' },
5
+ { type: 'blank', label: 'Blank', color: '#f97316' },
6
+ { type: 'qc', label: 'QC', color: '#8b5cf6' },
7
+ { type: 'iqc', label: 'IQC', color: '#ec4899' },
8
8
  ]
@@ -1,5 +1,4 @@
1
1
  import type { HeatmapConfig, Well, WellPlateSelectionMode, WellShape } from '../types'
2
- import { sampleTypeMarker } from '../utils/sampleTypeMarker'
3
2
  import {
4
3
  DEFAULT_SAMPLE_TYPE_COLORS,
5
4
  getHeatmapColor,
@@ -55,13 +54,15 @@ export function wellStyle(well: Well, state: WellStyleState): Record<string, str
55
54
  ?? state.sampleColors[well.sampleType] ?? DEFAULT_SAMPLE_TYPE_COLORS[well.sampleType])
56
55
  if (!groupColor) return {} // empty / filled wells are styled by well-plate.css
57
56
 
58
- const { background, border } = sampleTypeMarker(well.sampleType!, groupColor).style
59
- return { backgroundColor: background, border }
57
+ return sampleTint(groupColor)
60
58
  }
61
59
 
62
- /** In-well glyph of the 3a sample-type marker: `Q` for QC, `i` for iQC. */
63
- export function sampleTypeGlyph(well: Well): string | null {
64
- return well.sampleType ? sampleTypeMarker(well.sampleType, '').glyph : null
60
+ /** 15% fill and 40% outline of a sample color; color-mix accepts hex and `var()` tokens alike. */
61
+ export function sampleTint(color: string): { backgroundColor: string; border: string } {
62
+ return {
63
+ backgroundColor: `color-mix(in srgb, ${color} 15%, transparent)`,
64
+ border: `1px solid color-mix(in srgb, ${color} 40%, transparent)`,
65
+ }
65
66
  }
66
67
 
67
68
  let sampleTypeIndicatorWarned = false
@@ -89,10 +90,10 @@ export function wellBadge(
89
90
  if (!showBadges || !well.metadata) return null
90
91
  const count = well.metadata.injectionCount as number | undefined
91
92
  if (count && count > 1) {
92
- return { text: String(count), color: 'var(--text-primary)' }
93
+ return { text: String(count), color: '#6366f1' }
93
94
  }
94
95
  if (well.metadata.customMethod) {
95
- return { text: '', color: 'var(--color-purple)' } // custom method: a dot, no glyph
96
+ return { text: '+', color: '#ec4899' }
96
97
  }
97
98
  return null
98
99
  }
@@ -25,7 +25,7 @@ import {
25
25
  } from './WellPlate.geometry'
26
26
  import { useWellPlateInteraction } from './WellPlate.interaction'
27
27
  import {
28
- sampleTypeGlyph,
28
+ sampleTint,
29
29
  warnSampleTypeIndicatorDeprecated,
30
30
  wellBadge,
31
31
  wellClasses,
@@ -34,7 +34,6 @@ import {
34
34
  } from './WellPlate.rendering'
35
35
  import { DEFAULT_WELL_LEGEND_ITEMS } from './WellPlate.legend'
36
36
  import DataStateInternal from './internal/DataStateInternal.vue'
37
- import { sampleTypeMarker } from '../utils/sampleTypeMarker'
38
37
 
39
38
  interface Props {
40
39
  modelValue?: string[]
@@ -476,27 +475,13 @@ const tableStyle = computed(() => ({
476
475
  >
477
476
  {{ well.id }}
478
477
  </span>
479
- <!-- Sample-type marker glyph (Q / i) -->
480
- <span
481
- v-else-if="sampleTypeGlyph(well)"
482
- class="mint-well-plate__indicator"
483
- >
484
- {{ sampleTypeGlyph(well) }}
485
- </span>
486
- <!-- The glyph moves to a corner when a label or well ID takes the centre -->
487
- <span
488
- v-if="sampleTypeGlyph(well) && (getWellLabel(well) || props.showWellIds)"
489
- class="mint-well-plate__corner-glyph"
490
- >
491
- {{ sampleTypeGlyph(well) }}
492
- </span>
493
478
 
494
479
  <!-- Badge (injection count or custom method) -->
495
480
  <span
496
481
  v-if="getWellBadge(well)"
497
- :class="['mint-well-plate__badge', { 'mint-well-plate__badge--dot': !getWellBadge(well)?.text }]"
482
+ class="mint-well-plate__badge"
498
483
  :style="{ backgroundColor: getWellBadge(well)?.color }"
499
- :title="getWellBadge(well)?.text ? `${getWellBadge(well)?.text}x injections` : 'Custom method'"
484
+ :title="getWellBadge(well)?.text === '+' ? 'Custom method' : `${getWellBadge(well)?.text ?? ''}x injections`"
500
485
  >
501
486
  {{ getWellBadge(well)?.text }}
502
487
  </span>
@@ -531,11 +516,9 @@ const tableStyle = computed(() => ({
531
516
  class="mint-well-plate__sample-legend-item"
532
517
  >
533
518
  <span
534
- v-for="marker in [sampleTypeMarker(item.type, item.color)]"
535
- :key="item.type"
536
519
  class="mint-well-plate__sample-legend-dot"
537
- :style="marker.style"
538
- >{{ marker.glyph }}</span>
520
+ :style="sampleTint(item.color)"
521
+ />
539
522
  <span class="mint-well-plate__sample-legend-text">{{ item.label }}</span>
540
523
  </div>
541
524
  </div>
@@ -48,7 +48,8 @@ export { default as GeneratedResultRenderer } from './GeneratedResultRenderer.vu
48
48
  export { default as GeneratedPlotlyResult } from './GeneratedPlotlyResult.vue'
49
49
  export { default as ComponentBindingRenderer } from './ComponentBindingRenderer.vue'
50
50
  export { default as ControlWorkspaceView } from './ControlWorkspaceView.vue'
51
- export { default as DoseDesignWorkspaceView } from './DoseDesignWorkspaceView.vue'
51
+ /** Complete ControlWorkspaceView page shell for WellPlate + DoseCalculator dose design. @deprecated Scheduled for removal in MINT 1.4; use `ControlWorkspaceView` instead, with `defineDoseDesignControlModel()`. */
52
+ export { /** @deprecated Removed in MINT 1.4. */ default as DoseDesignWorkspaceView } from './DoseDesignWorkspaceView.vue'
52
53
  export { default as AppContainer } from './AppContainer.vue'
53
54
  export { default as PluginIcon } from './PluginIcon.vue'
54
55
  export { default as LayoutResizeHandle } from './LayoutResizeHandle.vue'
@@ -69,30 +70,43 @@ export { default as AdductText } from './AdductText.vue'
69
70
  export { default as SettingsModal } from './SettingsModal.vue'
70
71
 
71
72
  // Biological experiment components
72
- export { default as BioTemplateRenderer } from './BioTemplateRenderer.vue'
73
- export { default as BioTemplateExperimentWorkspaceView } from './BioTemplateExperimentWorkspaceView.vue'
74
- export { default as BioTemplatePackWorkspaceView } from './BioTemplatePackWorkspaceView.vue'
75
- export { default as BioTemplatePresetWorkspaceView } from './BioTemplatePresetWorkspaceView.vue'
73
+ /** Render biology template envelopes with matching SDK components such as WellPlate, DoseCalculator, and DataFrame. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
74
+ export { /** @deprecated Removed in MINT 1.4. */ default as BioTemplateRenderer } from './BioTemplateRenderer.vue'
75
+ /** Current-experiment template workspace with load/save/reset controls and WellPlate/DataFrame/DoseCalculator previews. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
76
+ export { /** @deprecated Removed in MINT 1.4. */ default as BioTemplateExperimentWorkspaceView } from './BioTemplateExperimentWorkspaceView.vue'
77
+ /** Complete current-experiment workspace for a curated biology template pack with save/load controls and component-prop slots. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
78
+ export { /** @deprecated Removed in MINT 1.4. */ default as BioTemplatePackWorkspaceView } from './BioTemplatePackWorkspaceView.vue'
79
+ /** Complete editable biology template preset workspace for WellPlate/DoseCalculator, LC-MS, qPCR, generated controls, preview components, and current-experiment save wiring. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
80
+ export { /** @deprecated Removed in MINT 1.4. */ default as BioTemplatePresetWorkspaceView } from './BioTemplatePresetWorkspaceView.vue'
76
81
  export { default as WellPlate } from './WellPlate.vue'
77
82
  export { default as PlateEditor } from './PlateEditor.vue'
78
- export { default as SampleLegend } from './SampleLegend.vue'
79
- export { default as ExperimentTimeline } from './ExperimentTimeline.vue'
83
+ /** Clickable color-swatch legend for plate sample types, supporting selection, count display, add/remove, and custom color assignment. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
84
+ export { /** @deprecated Removed in MINT 1.4. */ default as SampleLegend } from './SampleLegend.vue'
85
+ /** Horizontal or vertical timeline of ordered protocol steps with status indicators, drag-to-reorder, and step-type iconography. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
86
+ export { /** @deprecated Removed in MINT 1.4. */ default as ExperimentTimeline } from './ExperimentTimeline.vue'
80
87
 
81
88
  // Sample management components
89
+ /** Interactive sample list with drag-and-drop group assignment, color labeling, and smart-group support. */
82
90
  export { default as SampleSelector } from './SampleSelector.vue'
83
- /** Smart Group modal wired to the `useAutoGroup` engine. @deprecated Scheduled for removal in MINT 1.4; use `SmartGroupModal` instead. */
84
- export { default as AutoGroupModal } from './AutoGroupModal.vue'
85
- export { default as GroupAssigner } from './GroupAssigner.vue'
86
- export { default as SmartGroupModal } from './SmartGroupModal.vue'
87
- export { default as SmartGroupFieldRecipe } from './SmartGroupFieldRecipe.vue'
88
- export { default as SmartGroupManual } from './SmartGroupManual.vue'
91
+ /** Smart Group modal wired to the `useAutoGroup` engine. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
92
+ export { /** @deprecated Removed in MINT 1.4. */ default as AutoGroupModal } from './AutoGroupModal.vue'
93
+ /** Drag-and-drop two-zone assigner for splitting sample groups into control and treatment arms. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
94
+ export { /** @deprecated Removed in MINT 1.4. */ default as GroupAssigner } from './GroupAssigner.vue'
95
+ /** Smart Group modal with the auto Field Recipe and the manual cohort builder. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
96
+ export { /** @deprecated Removed in MINT 1.4. */ default as SmartGroupModal } from './SmartGroupModal.vue'
97
+ /** Smart Group auto mode: groups samples by a field recipe. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
98
+ export { /** @deprecated Removed in MINT 1.4. */ default as SmartGroupFieldRecipe } from './SmartGroupFieldRecipe.vue'
99
+ /** Smart Group manual mode: builds cohorts from selected samples. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
100
+ export { /** @deprecated Removed in MINT 1.4. */ default as SmartGroupManual } from './SmartGroupManual.vue'
89
101
 
90
102
  // Lab/Experiment components
91
103
  export { default as MoleculeInput } from './MoleculeInput.vue'
92
104
  export { default as ConcentrationInput } from './ConcentrationInput.vue'
93
105
  export { default as DoseCalculator } from './DoseCalculator.vue'
94
- export { default as ReagentEditor, type ReagentDefinition } from './ReagentEditor.vue'
95
- export { default as ReagentList } from './ReagentList.vue'
106
+ /** Editor for a single reagent definition with axis orientation, dilution-series generator, and position-count validation for 96/384-well plates. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
107
+ export { /** @deprecated Removed in MINT 1.4. */ default as ReagentEditor, type ReagentDefinition } from './ReagentEditor.vue'
108
+ /** Sortable, searchable table of experiment reagents showing lot number, expiry, storage conditions, and stock level with low-stock warnings. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
109
+ export { /** @deprecated Removed in MINT 1.4. */ default as ReagentList } from './ReagentList.vue'
96
110
  export { default as SampleHierarchyTree } from './SampleHierarchyTree.vue'
97
111
  export { default as ProtocolStepEditor } from './ProtocolStepEditor.vue'
98
112
 
@@ -100,7 +114,7 @@ export { default as ProtocolStepEditor } from './ProtocolStepEditor.vue'
100
114
  export { default as PlotlyChart } from './PlotlyChart.vue'
101
115
  export { default as ScientificNumber } from './ScientificNumber.vue'
102
116
  export { default as ChemicalFormula } from './ChemicalFormula.vue'
103
- /** Displays LC/MS or GC/MS acquisition sequence progress with ETA and remaining-time labels. @deprecated Scheduled for removal in MINT 1.4; instrument UI moves to the mld-ms plugins. */
117
+ /** Displays LC/MS or GC/MS acquisition sequence progress with ETA and remaining-time labels. */
104
118
  export { default as SequenceProgressBar } from './SequenceProgressBar.vue'
105
119
  /** Status badge for instrument states: a tone dot (or dash / hollow shape for offline, never-reported and inactive) plus a label. */
106
120
  export { default as InstrumentStateBadge } from './InstrumentStateBadge.vue'
@@ -138,6 +152,7 @@ export { default as ArtifactSelectorModal } from './ArtifactSelectorModal.vue'
138
152
  export { default as GeneratedPathInput } from './GeneratedPathInput.vue'
139
153
  export { default as ArtifactSaveDialog } from './ArtifactSaveDialog.vue'
140
154
  export { default as ExperimentPopover } from './ExperimentPopover.vue'
141
- export { default as FitPanel } from './FitPanel.vue'
155
+ /** Control panel for running a curve-fit job with progress bar, results table, and error display. @deprecated Scheduled for removal in MINT 1.4; no SDK replacement. */
156
+ export { /** @deprecated Removed in MINT 1.4. */ default as FitPanel } from './FitPanel.vue'
142
157
 
143
158
  export { default as FilePicker } from './FilePicker.vue'
@@ -89,7 +89,7 @@ function handleSelect(page: PageSelectorItem) {
89
89
  </span>
90
90
  </button>
91
91
 
92
- <div v-if="isOpen" class="mint-popover mint-page-selector__menu" role="menu">
92
+ <div v-if="isOpen" class="mint-page-selector__menu" role="menu">
93
93
  <div class="mint-page-selector__menu-title">Go to</div>
94
94
  <ActionItemInternal
95
95
  v-for="page in normalizedPages"
@@ -98,8 +98,8 @@ function handleSelect(page: PageSelectorItem) {
98
98
  :to="page.to"
99
99
  :disabled="page.disabled"
100
100
  :class="[
101
- 'mint-menu-item mint-page-selector__item',
102
- { 'mint-menu-item--selected mint-page-selector__item--active': page.id === current?.id },
101
+ 'mint-page-selector__item',
102
+ { 'mint-page-selector__item--active': page.id === current?.id },
103
103
  { 'mint-page-selector__item--disabled': page.disabled },
104
104
  ]"
105
105
  role="menuitem"
@@ -121,9 +121,6 @@ function handleSelect(page: PageSelectorItem) {
121
121
  </slot>
122
122
  </span>
123
123
  <span class="mint-page-selector__item-label">{{ page.label }}</span>
124
- <svg class="mint-menu-item__check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
125
- <path d="M3.5 8.5l3 3 6-7" />
126
- </svg>
127
124
  </ActionItemInternal>
128
125
  </div>
129
126
  </div>
@@ -22,6 +22,8 @@ const props = defineProps<{
22
22
  loading: boolean
23
23
  error: string
24
24
  query: string
25
+ /** Search results stopped at a limit; the rows are the first matches only. */
26
+ truncated?: boolean
25
27
  mode: PickerSelectionMode
26
28
  selected: (node: PickerNode) => boolean
27
29
  selectable: (node: PickerNode) => boolean
@@ -171,6 +173,9 @@ function parts(name: string) {
171
173
  <div class="mint-picker__list-head" aria-hidden="true">
172
174
  <span /><span>Name</span><span>Size</span><span>Modified</span>
173
175
  </div>
176
+ <p v-if="truncated && !loading && !error && rows.length" class="mint-picker__truncated" role="status">
177
+ Showing the first {{ rows.length }} matches. Search a smaller folder or use a more specific search to see the rest.
178
+ </p>
174
179
  <div v-if="loading" class="mint-picker__skeleton">
175
180
  <Skeleton v-for="n in 6" :key="n" width="100%" height="1.25rem" />
176
181
  </div>
@@ -180,10 +185,14 @@ function parts(name: string) {
180
185
  </div>
181
186
  <div v-else-if="!rows.length" class="mint-picker__empty" role="status">
182
187
  <strong>{{
183
- query ? 'No matching folders or files' : emptyTitle || 'No readable files'
188
+ truncated
189
+ ? 'Search stopped before finding a match'
190
+ : query ? 'No matching folders or files' : emptyTitle || 'No readable files'
184
191
  }}</strong>
185
192
  <span>{{
186
- query ? 'Try a different search or include subfolders.' : 'Choose another location.'
193
+ truncated
194
+ ? 'Search a smaller folder or use a more specific search.'
195
+ : query ? 'Try a different search or include subfolders.' : 'Choose another location.'
187
196
  }}</span>
188
197
  </div>
189
198
  <template v-for="row in rows" v-else :key="row.node.path">
@@ -1,21 +1,25 @@
1
1
  <script setup lang="ts" generic="T">
2
2
  /**
3
3
  * Searchable, optionally grouped single-select list shared by the experiment and
4
- * artifact selector modals (8b): 44px two-line rows, trailing check, skeleton,
4
+ * artifact selector modals: two-line rows (title, then code + meta) with status and date
5
+ * in a right-hand column, a filled selected row, skeleton,
5
6
  * error and empty states, and the combobox keyboard model (↑ ↓ Enter).
6
7
  */
7
8
  import { computed, nextTick, reactive, ref, useId, watch } from 'vue'
8
9
  import BaseInput from '../BaseInput.vue'
9
10
  import Skeleton from '../Skeleton.vue'
10
11
  import EmptyState from '../EmptyState.vue'
12
+ import type { PillVariant } from '../../types'
11
13
 
12
14
  export interface ResourcePickerRow {
13
- /** Identifier shown in mono before the title, e.g. an experiment code. */
15
+ /** Identifier shown as a gray pill at the start of the second line, e.g. an experiment code. */
14
16
  code?: string
15
17
  title: string
16
- /** Second-line facts, joined with " · ". */
18
+ /** Second-line facts after the code, joined with " · ". */
17
19
  meta?: string[]
18
- /** Rendered last on the second line, in mono. */
20
+ /** Right column, above the date, tinted by its variant. */
21
+ status?: { label: string; variant: PillVariant }
22
+ /** Right column, in mono. */
19
23
  date?: string
20
24
  }
21
25
 
@@ -206,36 +210,26 @@ watch(
206
210
  @mouseenter="activeIndex = indexOf(item)"
207
211
  >
208
212
  <span class="mint-resource-picker__text">
209
- <span class="mint-resource-picker__title"
210
- ><template v-if="describe(item).code"
211
- ><span class="mint-resource-picker__code">{{ describe(item).code }}</span> · </template
212
- >{{ describe(item).title }}</span
213
+ <span class="mint-resource-picker__title">{{ describe(item).title }}</span>
214
+ <span v-if="describe(item).code || describe(item).meta?.length" class="mint-resource-picker__meta"
215
+ ><span v-if="describe(item).code" class="mint-resource-picker__code mint-exp-code mint-exp-code--sm">{{
216
+ describe(item).code
217
+ }}</span
218
+ ><span v-if="describe(item).meta?.length">{{ describe(item).meta!.join(' · ') }}</span></span
213
219
  >
220
+ </span>
221
+ <span v-if="describe(item).status || describe(item).date" class="mint-resource-picker__aside">
214
222
  <span
215
- v-if="describe(item).meta?.length || describe(item).date"
216
- class="mint-resource-picker__meta"
217
- >{{ [...(describe(item).meta ?? [])].join(' · ')
218
- }}<template v-if="describe(item).date"
219
- >{{ describe(item).meta?.length ? ' · ' : ''
220
- }}<span class="mint-resource-picker__date">{{ describe(item).date }}</span></template
221
- ></span
223
+ v-if="describe(item).status"
224
+ class="mint-resource-picker__status"
225
+ :class="`mint-resource-picker__status--${describe(item).status!.variant}`"
226
+ >{{ describe(item).status!.label }}</span
222
227
  >
228
+ <span v-if="describe(item).date" class="mint-resource-picker__date">{{ describe(item).date }}</span>
223
229
  </span>
224
230
  <span v-if="$slots.actions" class="mint-resource-picker__actions" @click.stop>
225
231
  <slot name="actions" :item="item" />
226
232
  </span>
227
- <span class="mint-resource-picker__check-slot" aria-hidden="true">
228
- <svg
229
- v-if="itemKey(item) === selectedKey"
230
- class="mint-resource-picker__check"
231
- viewBox="0 0 16 16"
232
- fill="none"
233
- stroke="currentColor"
234
- stroke-width="2"
235
- >
236
- <path d="M3.5 8.5l3 3 6-7" />
237
- </svg>
238
- </span>
239
233
  </div>
240
234
  </template>
241
235
  </div>
@@ -1,5 +1,5 @@
1
1
  // Internal engine for `useAutoGroup` / `AutoGroupModal`. Deprecated: scheduled
2
- // for removal in MINT 1.4 together with `useAutoGroup`; `SmartGroupModal` replaces it.
2
+ // for removal in MINT 1.4 together with `useAutoGroup`; no SDK replacement.
3
3
 
4
4
  export { splitMulti, pickPrimaryDelimiter } from './tokenize'
5
5
  export type { Delimiter } from './tokenize'