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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/README.md +9 -1
  2. package/dist/__tests__/components/LcmsSequenceTable.test.d.ts +1 -0
  3. package/dist/__tests__/components/ProgressBar.test.d.ts +1 -0
  4. package/dist/__tests__/components/RackEditor.test.d.ts +1 -0
  5. package/dist/__tests__/components/SequenceProgressBar.test.d.ts +1 -0
  6. package/dist/__tests__/composables/useExperimentSamples.test.d.ts +1 -0
  7. package/dist/__tests__/utils/instrument.test.d.ts +1 -0
  8. package/dist/__tests__/utils/lcms.test.d.ts +1 -0
  9. package/dist/__tests__/utils/permissions.test.d.ts +1 -0
  10. package/dist/__tests__/utils/rack.test.d.ts +1 -0
  11. package/dist/{auth-CBG3bWEc.js → auth-B7g4J4ZF.js} +99 -5
  12. package/dist/auth-B7g4J4ZF.js.map +1 -0
  13. package/dist/components/AutoGroupModal.vue.d.ts +1 -1
  14. package/dist/components/BaseCheckbox.vue.d.ts +1 -1
  15. package/dist/components/BaseToggle.vue.d.ts +2 -2
  16. package/dist/components/BioTemplateExperimentWorkspaceView.vue.d.ts +1 -1
  17. package/dist/components/BioTemplatePackWorkspaceView.vue.d.ts +1 -1
  18. package/dist/components/BioTemplatePresetWorkspaceView.vue.d.ts +1 -1
  19. package/dist/components/DoseDesignWorkspaceView.vue.d.ts +1 -1
  20. package/dist/components/FormulaInput.vue.d.ts +1 -1
  21. package/dist/components/InstrumentAlertLog.vue.d.ts +22 -0
  22. package/dist/components/InstrumentStateBadge.vue.d.ts +11 -0
  23. package/dist/components/InstrumentStatusCard.vue.d.ts +13 -0
  24. package/dist/components/LcmsSequenceTable.vue.d.ts +26 -0
  25. package/dist/components/ProgressBar.vue.d.ts +1 -0
  26. package/dist/components/RackEditor.vue.d.ts +41 -3
  27. package/dist/components/ReagentList.vue.d.ts +1 -1
  28. package/dist/components/SampleSelector.vue.d.ts +5 -2
  29. package/dist/components/SegmentedControl.vue.d.ts +2 -0
  30. package/dist/components/SequenceInput.vue.d.ts +1 -1
  31. package/dist/components/SequenceProgressBar.vue.d.ts +15 -0
  32. package/dist/components/SettingsModal.vue.d.ts +3 -1
  33. package/dist/components/TagsInput.vue.d.ts +1 -1
  34. package/dist/components/WellPlate.vue.d.ts +42 -3
  35. package/dist/components/index.d.ts +5 -0
  36. package/dist/components/index.js +3 -3
  37. package/dist/{components-5KSfsVqf.js → components-BhK-dW99.js} +2091 -1051
  38. package/dist/components-BhK-dW99.js.map +1 -0
  39. package/dist/composables/experimentDesignData.d.ts +17 -0
  40. package/dist/composables/index.d.ts +2 -0
  41. package/dist/composables/index.js +4 -4
  42. package/dist/composables/useControlSchema.d.ts +11 -0
  43. package/dist/composables/useExperimentData.d.ts +11 -3
  44. package/dist/composables/useExperimentSamples.d.ts +42 -0
  45. package/dist/composables/usePlatformContext.d.ts +54 -0
  46. package/dist/{composables-D4Myb30a.js → composables-Bg7CFuNz.js} +5 -3
  47. package/dist/composables-Bg7CFuNz.js.map +1 -0
  48. package/dist/index.d.ts +4 -0
  49. package/dist/index.js +168 -6
  50. package/dist/index.js.map +1 -0
  51. package/dist/install.js +2 -2
  52. package/dist/instrument.d.ts +7 -0
  53. package/dist/lcms.d.ts +27 -0
  54. package/dist/permissions.d.ts +46 -0
  55. package/dist/stores/auth.d.ts +74 -2
  56. package/dist/stores/index.js +1 -1
  57. package/dist/styles.css +3316 -1216
  58. package/dist/templates/builders.d.ts +7 -3
  59. package/dist/templates/index.d.ts +2 -2
  60. package/dist/templates/index.js +2 -2
  61. package/dist/templates/presets.d.ts +12 -0
  62. package/dist/templates/types.d.ts +16 -1
  63. package/dist/{templates-BSlxwV2c.js → templates-BorLR_7p.js} +313 -3
  64. package/dist/templates-BorLR_7p.js.map +1 -0
  65. package/dist/types/auth.d.ts +2 -0
  66. package/dist/types/components.d.ts +32 -3
  67. package/dist/types/form-builder.d.ts +2 -1
  68. package/dist/types/index.d.ts +4 -1
  69. package/dist/types/instrument.d.ts +56 -0
  70. package/dist/types/platform.d.ts +3 -0
  71. package/dist/{useExperimentData-BbbdI5xT.js → useProtocolTemplates-n6AJqSqv.js} +534 -359
  72. package/dist/useProtocolTemplates-n6AJqSqv.js.map +1 -0
  73. package/dist/utils/rack.d.ts +47 -0
  74. package/package.json +1 -1
  75. package/src/__tests__/components/AppTopBar.test.ts +15 -0
  76. package/src/__tests__/components/BaseTabs.test.ts +15 -0
  77. package/src/__tests__/components/LcmsSequenceTable.test.ts +57 -0
  78. package/src/__tests__/components/ProgressBar.test.ts +18 -0
  79. package/src/__tests__/components/RackEditor.test.ts +125 -0
  80. package/src/__tests__/components/SampleSelector.test.ts +25 -0
  81. package/src/__tests__/components/SegmentedControl.test.ts +45 -0
  82. package/src/__tests__/components/SequenceProgressBar.test.ts +39 -0
  83. package/src/__tests__/components/SettingsModal.test.ts +83 -2
  84. package/src/__tests__/composables/useControlSchema.test.ts +4 -0
  85. package/src/__tests__/composables/useExperimentData.test.ts +23 -0
  86. package/src/__tests__/composables/useExperimentSamples.test.ts +91 -0
  87. package/src/__tests__/templates/templates.test.ts +86 -0
  88. package/src/__tests__/utils/instrument.test.ts +47 -0
  89. package/src/__tests__/utils/lcms.test.ts +73 -0
  90. package/src/__tests__/utils/permissions.test.ts +50 -0
  91. package/src/__tests__/utils/rack.test.ts +120 -0
  92. package/src/components/AppTopBar.vue +1 -0
  93. package/src/components/BaseTabs.vue +22 -1
  94. package/src/components/InstrumentAlertLog.vue +191 -0
  95. package/src/components/InstrumentStateBadge.vue +50 -0
  96. package/src/components/InstrumentStatusCard.vue +188 -0
  97. package/src/components/LcmsSequenceTable.vue +191 -0
  98. package/src/components/ProgressBar.vue +3 -0
  99. package/src/components/RackEditor.vue +73 -2
  100. package/src/components/SampleSelector.vue +28 -9
  101. package/src/components/SegmentedControl.story.vue +17 -0
  102. package/src/components/SegmentedControl.vue +14 -3
  103. package/src/components/SequenceProgressBar.vue +71 -0
  104. package/src/components/SettingsModal.vue +42 -2
  105. package/src/components/WellPlate.vue +142 -21
  106. package/src/components/index.ts +5 -0
  107. package/src/components/internal/WellEditPopupInternal.vue +1 -0
  108. package/src/composables/experimentDesignData.ts +182 -0
  109. package/src/composables/index.ts +14 -0
  110. package/src/composables/useAuth.ts +4 -0
  111. package/src/composables/useAutoGroup.ts +5 -1
  112. package/src/composables/useControlSchema.ts +21 -0
  113. package/src/composables/useExperimentData.ts +57 -16
  114. package/src/composables/useExperimentSamples.ts +142 -0
  115. package/src/index.ts +27 -0
  116. package/src/instrument.ts +90 -0
  117. package/src/lcms.ts +108 -0
  118. package/src/permissions.ts +143 -0
  119. package/src/stores/auth.ts +31 -3
  120. package/src/styles/components/instrument-monitor.css +478 -0
  121. package/src/styles/components/lcms-sequence-table.css +189 -0
  122. package/src/styles/components/sequence-progress-bar.css +63 -0
  123. package/src/styles/components/tabs.css +9 -0
  124. package/src/styles/components/well-edit-popup.css +7 -1
  125. package/src/styles/components/well-plate.css +5 -0
  126. package/src/styles/index.css +3 -0
  127. package/src/templates/builders.ts +201 -0
  128. package/src/templates/controlSchemas.ts +68 -0
  129. package/src/templates/index.ts +2 -0
  130. package/src/templates/presets.ts +23 -0
  131. package/src/templates/types.ts +17 -0
  132. package/src/types/auth.ts +3 -0
  133. package/src/types/components.ts +45 -3
  134. package/src/types/form-builder.ts +2 -1
  135. package/src/types/index.ts +35 -0
  136. package/src/types/instrument.ts +61 -0
  137. package/src/types/platform.ts +4 -0
  138. package/src/utils/rack.ts +209 -0
  139. package/dist/auth-CBG3bWEc.js.map +0 -1
  140. package/dist/components-5KSfsVqf.js.map +0 -1
  141. package/dist/composables-D4Myb30a.js.map +0 -1
  142. package/dist/templates-BSlxwV2c.js.map +0 -1
  143. package/dist/useExperimentData-BbbdI5xT.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  /** Multi-rack editor for managing collections of well-plate racks with add/remove/reorder controls and per-well editing. */
3
3
  import { ref, computed, watch } from 'vue'
4
- import type { Rack, SlotPosition, WellPlateFormat, WellPlateSize, WellEditData, Well } from '../types'
4
+ import type { Rack, SlotPosition, WellPlateFormat, WellPlateSize, WellEditData, Well, WellEditField, WellSampleDropData, RackSampleDropMapper } from '../types'
5
5
  import { useRackEditor } from '../composables/useRackEditor'
6
6
  import WellPlate from './WellPlate.vue'
7
7
 
@@ -16,6 +16,8 @@ interface Props {
16
16
  wellPlateSize?: WellPlateSize
17
17
  showLegend?: boolean
18
18
  showBadges?: boolean
19
+ allowSampleDrop?: boolean
20
+ sampleDropMapper?: RackSampleDropMapper
19
21
  }
20
22
 
21
23
  const props = withDefaults(defineProps<Props>(), {
@@ -29,6 +31,8 @@ const props = withDefaults(defineProps<Props>(), {
29
31
  wellPlateSize: 'md',
30
32
  showLegend: true,
31
33
  showBadges: true,
34
+ allowSampleDrop: false,
35
+ sampleDropMapper: undefined,
32
36
  })
33
37
 
34
38
  const emit = defineEmits<{
@@ -38,6 +42,24 @@ const emit = defineEmits<{
38
42
  'rack-remove': [rackId: string]
39
43
  'rack-reorder': [rackIds: string[]]
40
44
  'well-edit': [rackId: string, wellId: string, data: WellEditData]
45
+ 'sample-drop': [rackId: string, wellId: string, data: WellSampleDropData, event: DragEvent]
46
+ }>()
47
+
48
+ interface RackWellEditorSlotProps {
49
+ rack?: Rack
50
+ rackId: string
51
+ wellId: string
52
+ wellData?: Partial<Well>
53
+ editFields: WellEditField[]
54
+ defaultInjectionVolume: number
55
+ position: { x: number; y: number }
56
+ save: (data: WellEditData) => void
57
+ clear: () => void
58
+ close: () => void
59
+ }
60
+
61
+ defineSlots<{
62
+ 'well-editor'?: (props: RackWellEditorSlotProps) => unknown
41
63
  }>()
42
64
 
43
65
  const editor = useRackEditor(props.modelValue, {
@@ -214,6 +236,37 @@ function handleWellClear(wellId: string) {
214
236
  editor.clearWell(editor.activeRack.value.id, wellId)
215
237
  }
216
238
 
239
+ function handleSampleDrop(wellId: string, data: WellSampleDropData, event: DragEvent) {
240
+ const rack = editor.activeRack.value
241
+ if (!rack) return
242
+
243
+ emit('sample-drop', rack.id, wellId, data, event)
244
+
245
+ const editData = props.sampleDropMapper?.(data, {
246
+ rack,
247
+ rackId: rack.id,
248
+ wellId,
249
+ event,
250
+ }) ?? defaultSampleDropEditData(wellId, data, rack)
251
+
252
+ if (!editData) return
253
+ handleWellEdit(wellId, { ...editData, wellId })
254
+ }
255
+
256
+ function defaultSampleDropEditData(wellId: string, data: WellSampleDropData, rack: Rack): WellEditData | null {
257
+ const label = data.label ?? data.sampleName ?? data.id ?? ''
258
+ if (!label.trim()) return null
259
+
260
+ return {
261
+ wellId,
262
+ label: label.trim(),
263
+ sampleType: data.sampleType ?? 'sample',
264
+ injectionVolume: data.injectionVolume ?? rack.injectionVolume,
265
+ injectionCount: data.injectionCount ?? 1,
266
+ customMethod: data.customMethod ?? '',
267
+ }
268
+ }
269
+
217
270
  // Computed well count per rack
218
271
  function getWellCount(rack: Rack): number {
219
272
  return Object.keys(rack.wells).length
@@ -359,9 +412,27 @@ const activeRackWells = computed(() => editor.activeRack.value?.wells ?? {})
359
412
  :readonly="readonly"
360
413
  :size="wellPlateSize"
361
414
  :show-sample-type-indicator="true"
415
+ :allow-sample-drop="allowSampleDrop && !readonly"
362
416
  @well-edit="handleWellEdit"
363
417
  @well-clear="handleWellClear"
364
- />
418
+ @sample-drop="handleSampleDrop"
419
+ >
420
+ <template v-if="$slots['well-editor']" #well-editor="slotProps">
421
+ <slot
422
+ name="well-editor"
423
+ :rack="editor.activeRack.value"
424
+ :rack-id="editor.activeRack.value?.id ?? ''"
425
+ :well-id="slotProps.wellId"
426
+ :well-data="slotProps.wellData"
427
+ :edit-fields="slotProps.editFields"
428
+ :default-injection-volume="slotProps.defaultInjectionVolume"
429
+ :position="slotProps.position"
430
+ :save="slotProps.save"
431
+ :clear="slotProps.clear"
432
+ :close="slotProps.close"
433
+ />
434
+ </template>
435
+ </WellPlate>
365
436
  </div>
366
437
  </div>
367
438
  </template>
@@ -11,23 +11,27 @@ import { useTextSearch } from '../composables/useTextSearch'
11
11
  import { useListSelection } from '../composables/useListSelection'
12
12
  import { useSampleGroups, type SampleMajorGroup } from '../composables/useSampleGroups'
13
13
  import { useExpansionSet } from '../composables/useExpansionSet'
14
+ import { useExperimentSamples } from '../composables/useExperimentSamples'
14
15
 
15
16
  interface Props {
16
- samples: string[]
17
+ samples?: string[]
17
18
  modelValue: string[]
18
19
  groups?: SampleGroup[]
19
20
  enableGrouping?: boolean
20
21
  enableSmartGroup?: boolean
21
22
  experimentId?: number
22
23
  designData?: Record<string, unknown>
24
+ autoloadExperimentData?: boolean
23
25
  }
24
26
 
25
27
  const props = withDefaults(defineProps<Props>(), {
28
+ samples: () => [],
26
29
  groups: () => [],
27
30
  enableGrouping: true,
28
31
  enableSmartGroup: true,
29
32
  experimentId: undefined,
30
33
  designData: undefined,
34
+ autoloadExperimentData: true,
31
35
  })
32
36
 
33
37
  const emit = defineEmits<{
@@ -66,8 +70,23 @@ const internalGroups = computed({
66
70
  set: (value) => emit('update:groups', value),
67
71
  })
68
72
 
73
+ const providedSamples = computed(() => props.samples)
74
+ const shouldLoadExperimentSamples = computed(() =>
75
+ props.autoloadExperimentData && providedSamples.value.length === 0,
76
+ )
77
+ const experimentSamples = useExperimentSamples({
78
+ experimentId: () => props.experimentId,
79
+ designData: () => props.designData,
80
+ enabled: shouldLoadExperimentSamples,
81
+ })
82
+ const resolvedSamples = computed(() =>
83
+ providedSamples.value.length > 0 ? providedSamples.value : experimentSamples.samples.value,
84
+ )
85
+ const resolvedExperimentId = computed(() => props.experimentId ?? experimentSamples.experimentId.value)
86
+ const resolvedDesignData = computed(() => props.designData ?? experimentSamples.designData.value ?? undefined)
87
+
69
88
  const sampleGroups = useSampleGroups({
70
- samples: () => props.samples,
89
+ samples: () => resolvedSamples.value,
71
90
  groups: internalGroups,
72
91
  })
73
92
  const hierarchicalGroups = sampleGroups.hierarchicalGroups
@@ -77,7 +96,7 @@ const groupingEnabled = computed(() => internalGroups.value.length > 0)
77
96
  const ungroupedSamples = sampleGroups.ungroupedSamples
78
97
 
79
98
  const sampleSearch = useTextSearch({
80
- items: () => props.samples,
99
+ items: () => resolvedSamples.value,
81
100
  query: searchQuery,
82
101
  getText: sample => sample,
83
102
  })
@@ -85,7 +104,7 @@ const filteredSamples = sampleSearch.filteredItems
85
104
 
86
105
  const sampleSelection = useListSelection({
87
106
  selected: () => props.modelValue,
88
- items: () => props.samples,
107
+ items: () => resolvedSamples.value,
89
108
  })
90
109
  const isAllSelected = sampleSelection.isAllSelected
91
110
 
@@ -433,7 +452,7 @@ function addNewGroup() {
433
452
  class="mint-sample-selector__checkbox"
434
453
  />
435
454
  <span class="mint-sample-selector__select-all-label">Select All</span>
436
- <span class="mint-sample-selector__select-all-count">{{ samples.length }} samples</span>
455
+ <span class="mint-sample-selector__select-all-count">{{ resolvedSamples.length }} samples</span>
437
456
  </label>
438
457
 
439
458
  <!-- Action Buttons Row -->
@@ -444,7 +463,7 @@ function addNewGroup() {
444
463
  v-if="enableSmartGroup"
445
464
  :variant="groupingEnabled ? 'primary' : 'secondary'"
446
465
  size="sm"
447
- :disabled="samples.length === 0"
466
+ :disabled="resolvedSamples.length === 0"
448
467
  class="mint-sample-selector__action-btn"
449
468
  @click="showSmartGroupModal = true"
450
469
  >
@@ -940,9 +959,9 @@ function addNewGroup() {
940
959
  <!-- Smart Grouping Modal -->
941
960
  <AutoGroupModal
942
961
  v-model="showSmartGroupModal"
943
- :samples="samples"
944
- :experiment-id="experimentId"
945
- :design-data="designData"
962
+ :samples="resolvedSamples"
963
+ :experiment-id="resolvedExperimentId"
964
+ :design-data="resolvedDesignData"
946
965
  @apply="handleSmartGroupApply"
947
966
  />
948
967
  </div>
@@ -33,7 +33,13 @@ const withDisabledOptions: SegmentedOption[] = [
33
33
  { value: 'deleted', label: 'Deleted', disabled: true },
34
34
  ]
35
35
 
36
+ const twoTabOptions: SegmentedOption[] = [
37
+ { value: 'table', label: 'Table' },
38
+ { value: 'chart', label: 'Chart' },
39
+ ]
40
+
36
41
  const cardSelectionP3 = ref('list')
42
+ const twoTabSelection = ref('table')
37
43
  </script>
38
44
 
39
45
  <template>
@@ -100,6 +106,17 @@ const cardSelectionP3 = ref('list')
100
106
  </div>
101
107
  </Variant>
102
108
 
109
+ <Variant title="Disabled By Value">
110
+ <div style="padding: 2rem; max-width: 500px; margin: 0 auto;">
111
+ <SegmentedControl
112
+ v-model="twoTabSelection"
113
+ :options="twoTabOptions"
114
+ :disabled-values="['chart']"
115
+ variant="simple"
116
+ />
117
+ </div>
118
+ </Variant>
119
+
103
120
  <Variant title="Not Full Width">
104
121
  <div style="padding: 2rem;">
105
122
  <SegmentedControl :model-value="'day'" :options="basicOptions" :full-width="false" />
@@ -11,6 +11,7 @@ interface Props {
11
11
  size?: SegmentedControlSize
12
12
  fullWidth?: boolean
13
13
  disabled?: boolean
14
+ disabledValues?: Array<string | number>
14
15
  }
15
16
 
16
17
  const props = withDefaults(defineProps<Props>(), {
@@ -18,6 +19,7 @@ const props = withDefaults(defineProps<Props>(), {
18
19
  size: 'md',
19
20
  fullWidth: false,
20
21
  disabled: false,
22
+ disabledValues: () => [],
21
23
  })
22
24
 
23
25
  const emit = defineEmits<{
@@ -25,9 +27,18 @@ const emit = defineEmits<{
25
27
  }>()
26
28
 
27
29
  const normalizedOptions = computed<SegmentedOption[]>(() => props.options.map(normalizeOptionInput))
30
+ const disabledValueSet = computed(() => new Set(props.disabledValues))
31
+
32
+ function isOptionDisabled(option: SegmentedOption) {
33
+ return option.disabled === true || disabledValueSet.value.has(option.value)
34
+ }
35
+
36
+ function isSelectionDisabled(option: SegmentedOption) {
37
+ return props.disabled || isOptionDisabled(option)
38
+ }
28
39
 
29
40
  function handleSelect(option: SegmentedOption) {
30
- if (props.disabled || option.disabled) return
41
+ if (isSelectionDisabled(option)) return
31
42
  emit('update:modelValue', option.value)
32
43
  }
33
44
 
@@ -56,13 +67,13 @@ function handleKeydown(event: KeyboardEvent, option: SegmentedOption) {
56
67
  type="button"
57
68
  role="radio"
58
69
  :aria-checked="modelValue === option.value"
59
- :disabled="disabled || option.disabled"
70
+ :disabled="isSelectionDisabled(option)"
60
71
  :class="[
61
72
  'mint-segmented-control__option',
62
73
  `mint-segmented-control__option--${variant}`,
63
74
  `mint-segmented-control__option--${size}`,
64
75
  modelValue === option.value ? 'mint-segmented-control__option--active' : '',
65
- option.disabled ? 'mint-segmented-control__option--disabled' : '',
76
+ isOptionDisabled(option) ? 'mint-segmented-control__option--disabled' : '',
66
77
  ]"
67
78
  @click="handleSelect(option)"
68
79
  @keydown="handleKeydown($event, option)"
@@ -0,0 +1,71 @@
1
+ <script setup lang="ts">
2
+ /** Displays LC/MS or GC/MS acquisition sequence progress with ETA and remaining-time labels. */
3
+ import { computed } from 'vue'
4
+ import {
5
+ formatSequenceEta,
6
+ formatSequenceRemaining,
7
+ sequenceProgressPercent,
8
+ } from '../instrument'
9
+ import type { SequenceProgress } from '../types'
10
+ import ProgressBar from './ProgressBar.vue'
11
+
12
+ interface Props {
13
+ progress: SequenceProgress
14
+ compact?: boolean
15
+ label?: string
16
+ showEta?: boolean
17
+ showRemaining?: boolean
18
+ }
19
+
20
+ const props = withDefaults(defineProps<Props>(), {
21
+ compact: false,
22
+ label: 'samples',
23
+ showEta: true,
24
+ showRemaining: true,
25
+ })
26
+
27
+ const percent = computed(() => sequenceProgressPercent(props.progress))
28
+ const remainingLabel = computed(() => formatSequenceRemaining(props.progress))
29
+ const etaLabel = computed(() => formatSequenceEta(props.progress))
30
+ const hasFooter = computed(() => {
31
+ if (props.compact) return false
32
+ return (props.showRemaining && remainingLabel.value) || (props.showEta && etaLabel.value)
33
+ })
34
+ </script>
35
+
36
+ <template>
37
+ <div class="mint-sequence-progress">
38
+ <div class="mint-sequence-progress__header">
39
+ <span class="mint-sequence-progress__label">
40
+ {{ progress.current_sample }} / {{ progress.total_samples }} {{ label }}
41
+ </span>
42
+ <span class="mint-sequence-progress__percent">{{ percent }}%</span>
43
+ </div>
44
+
45
+ <ProgressBar
46
+ :value="percent"
47
+ color="info"
48
+ size="sm"
49
+ aria-label="Sequence progress"
50
+ />
51
+
52
+ <div v-if="hasFooter" class="mint-sequence-progress__footer">
53
+ <span
54
+ v-if="showRemaining && remainingLabel"
55
+ class="mint-sequence-progress__remaining"
56
+ >
57
+ {{ remainingLabel }}
58
+ </span>
59
+ <span
60
+ v-if="showEta && etaLabel"
61
+ class="mint-sequence-progress__eta"
62
+ >
63
+ ETA {{ etaLabel }}
64
+ </span>
65
+ </div>
66
+ </div>
67
+ </template>
68
+
69
+ <style>
70
+ @import '../styles/components/sequence-progress-bar.css';
71
+ </style>
@@ -32,6 +32,9 @@ import {
32
32
  type ControlWorkspaceOptions,
33
33
  } from '../composables/useControlSchema'
34
34
  import { useSettingsStore, colorPalettes } from '../stores/settings'
35
+ import { useAuthStore } from '../stores/auth'
36
+ import { usePlatformContext } from '../composables/usePlatformContext'
37
+ import { canAccessByPolicy } from '../permissions'
35
38
  import {
36
39
  formSchemaFieldNames,
37
40
  pickExistingRecordKeys,
@@ -46,10 +49,12 @@ import type {
46
49
  SettingsTabInput,
47
50
  SettingsModalLayout,
48
51
  SettingsModalSchema,
52
+ SettingsUserType,
49
53
  FormSchema,
50
54
  FormSectionSchema,
51
55
  FormEnhancements,
52
56
  } from '../types'
57
+ import type { AccessControlled, PermissionUser } from '../permissions'
53
58
  import { normalizeItemInput } from '../utils/items'
54
59
 
55
60
  // Map our settings groups onto the form-builder's flat-section shape.
@@ -86,6 +91,8 @@ interface Props {
86
91
  values?: Record<string, unknown>
87
92
  /** Optional dynamic enhancements (validators, dynamic options, callbacks). */
88
93
  enhancements?: FormEnhancements<Record<string, unknown>>
94
+ /** Optional user type override for permission-filtered settings content. Defaults to SDK auth/platform context. */
95
+ userType?: SettingsUserType
89
96
  }
90
97
 
91
98
  const props = withDefaults(defineProps<Props>(), {
@@ -104,6 +111,8 @@ const emit = defineEmits<{
104
111
  }>()
105
112
 
106
113
  const settings = useSettingsStore()
114
+ const auth = useAuthStore()
115
+ const { user: platformUser } = usePlatformContext()
107
116
 
108
117
  const APPEARANCE_TAB_ID = 'appearance'
109
118
 
@@ -130,13 +139,34 @@ const resolvedControls = computed<ControlSchema | undefined>(() =>
130
139
  const resolvedControlOptions = computed<ControlWorkspaceOptions>(() =>
131
140
  mergeControlWorkspaceOptions(resolvedModel.value?.controlOptions ?? {}, props.controlOptions)
132
141
  )
133
- const settingsSchema = computed<SettingsModalSchema | undefined>(() =>
142
+ const sourceSettingsSchema = computed<SettingsModalSchema | undefined>(() =>
134
143
  props.schema ?? (
135
144
  resolvedControls.value
136
145
  ? controlsToSettingsSchema(resolvedControls.value, resolvedControlOptions.value)
137
146
  : undefined
138
147
  ),
139
148
  )
149
+ const currentAccessUser = computed<PermissionUser | null>(() => {
150
+ if (props.userType) {
151
+ return { role: props.userType === 'admin' ? 'admin' : 'user' }
152
+ }
153
+ return auth.userInfo ?? platformUser.value ?? null
154
+ })
155
+ const isAccessUserAuthenticated = computed(() =>
156
+ props.userType !== undefined || auth.isAuthenticated || !!platformUser.value,
157
+ )
158
+ const settingsSchema = computed<SettingsModalSchema | undefined>(() => {
159
+ if (!sourceSettingsSchema.value) return undefined
160
+ const groups = sourceSettingsSchema.value.groups.flatMap((group) => {
161
+ if (!canShowForCurrentUser(group)) return []
162
+
163
+ const fields = group.fields.filter(canShowForCurrentUser)
164
+ if (fields.length === 0) return []
165
+
166
+ return [{ ...group, fields }]
167
+ })
168
+ return { groups }
169
+ })
140
170
  const isSchemaDriven = computed(() => !!settingsSchema.value)
141
171
  const resolvedValues = computed<Record<string, unknown>>(() => ({
142
172
  ...(resolvedControlOptions.value.initialValues ?? {}),
@@ -201,7 +231,9 @@ const visibleSchemaGroups = computed(() =>
201
231
  : [],
202
232
  )
203
233
 
204
- const manualTabs = computed<SettingsTab[]>(() => props.tabs.map(normalizeItemInput))
234
+ const manualTabs = computed<SettingsTab[]>(() =>
235
+ props.tabs.map(normalizeItemInput).filter(canShowForCurrentUser)
236
+ )
205
237
 
206
238
  const allTabs = computed<SettingsTab[]>(() => {
207
239
  const base: SettingsTab[] = settingsSchema.value
@@ -256,6 +288,14 @@ function builderFieldNames(): string[] {
256
288
  function isControlModelBinding(model: ControlModel | ControlModelBinding): model is ControlModelBinding {
257
289
  return 'controls' in model && 'controlOptions' in model
258
290
  }
291
+
292
+ function canShowForCurrentUser(item: AccessControlled): boolean {
293
+ return canAccessByPolicy(
294
+ currentAccessUser.value,
295
+ item,
296
+ isAccessUserAuthenticated.value,
297
+ )
298
+ }
259
299
  </script>
260
300
 
261
301
  <template>