@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
@@ -0,0 +1,63 @@
1
+ .mint-sequence-progress {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 0.25rem;
5
+ min-width: 0;
6
+ }
7
+
8
+ .mint-sequence-progress__header {
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: space-between;
12
+ gap: 0.75rem;
13
+ min-width: 0;
14
+ }
15
+
16
+ .mint-sequence-progress__label {
17
+ min-width: 0;
18
+ overflow: hidden;
19
+ color: var(--text-muted, #64748b);
20
+ font-family: var(--font-mono, 'Fira Code', monospace);
21
+ font-size: 0.75rem;
22
+ text-overflow: ellipsis;
23
+ white-space: nowrap;
24
+ }
25
+
26
+ .mint-sequence-progress__percent {
27
+ flex: 0 0 auto;
28
+ color: var(--mint-info, #3b82f6);
29
+ font-family: var(--font-mono, 'Fira Code', monospace);
30
+ font-size: 0.6875rem;
31
+ font-weight: 700;
32
+ }
33
+
34
+ .mint-sequence-progress__footer {
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: space-between;
38
+ gap: 0.75rem;
39
+ min-width: 0;
40
+ }
41
+
42
+ .mint-sequence-progress__remaining {
43
+ min-width: 0;
44
+ overflow: hidden;
45
+ color: var(--text-muted, #64748b);
46
+ font-family: var(--font-mono, 'Fira Code', monospace);
47
+ font-size: 0.6875rem;
48
+ text-overflow: ellipsis;
49
+ white-space: nowrap;
50
+ }
51
+
52
+ .mint-sequence-progress__eta {
53
+ flex: 0 0 auto;
54
+ border: 1px solid color-mix(in srgb, var(--mint-info, #3b82f6) 24%, transparent);
55
+ border-radius: 999px;
56
+ background: color-mix(in srgb, var(--mint-info, #3b82f6) 10%, transparent);
57
+ color: var(--mint-info, #3b82f6);
58
+ font-family: var(--font-mono, 'Fira Code', monospace);
59
+ font-size: 0.6875rem;
60
+ font-weight: 700;
61
+ padding: 0.125rem 0.5rem;
62
+ white-space: nowrap;
63
+ }
@@ -9,10 +9,13 @@
9
9
  }
10
10
 
11
11
  .mint-tabs--pills {
12
+ width: fit-content;
13
+ max-width: 100%;
12
14
  gap: 0.5rem;
13
15
  padding: 0.25rem;
14
16
  background-color: var(--bg-tertiary);
15
17
  border-radius: var(--radius-md);
18
+ overflow-x: auto;
16
19
  }
17
20
 
18
21
  .mint-tab {
@@ -84,6 +87,12 @@
84
87
  color: currentColor;
85
88
  }
86
89
 
90
+ .mint-tab__icon--svg {
91
+ width: 1rem;
92
+ height: 1rem;
93
+ flex-shrink: 0;
94
+ }
95
+
87
96
  .mint-tab__badge {
88
97
  margin-left: 0.25rem;
89
98
  padding: 0.125rem 0.375rem;
@@ -120,7 +120,7 @@
120
120
  /* Sample type buttons */
121
121
  .mint-well-edit-popup__type-grid {
122
122
  display: grid;
123
- grid-template-columns: repeat(3, 1fr);
123
+ grid-template-columns: repeat(4, 1fr);
124
124
  gap: 0.25rem;
125
125
  }
126
126
 
@@ -154,6 +154,12 @@
154
154
  color: #8b5cf6;
155
155
  }
156
156
 
157
+ .mint-well-edit-popup__type-btn--iqc {
158
+ background-color: rgba(236, 72, 153, 0.15);
159
+ border-color: rgba(236, 72, 153, 0.4);
160
+ color: #ec4899;
161
+ }
162
+
157
163
  /* Injection count buttons */
158
164
  .mint-well-edit-popup__count-grid {
159
165
  display: grid;
@@ -183,6 +183,11 @@
183
183
  border: 1px solid rgba(139, 92, 246, 0.4);
184
184
  }
185
185
 
186
+ .mint-well-plate__well--iqc {
187
+ background-color: rgba(236, 72, 153, 0.15);
188
+ border: 1px solid rgba(236, 72, 153, 0.4);
189
+ }
190
+
186
191
  /* Condition header - drug label row */
187
192
  .mint-well-plate__condition-label {
188
193
  font-weight: 600;
@@ -20,6 +20,7 @@
20
20
  @import './components/file-uploader.css';
21
21
  @import './components/form-field.css';
22
22
  @import './components/icon-button.css';
23
+ @import './components/instrument-monitor.css';
23
24
  @import './components/input.css';
24
25
  @import './components/modal.css';
25
26
  @import './components/number-input.css';
@@ -46,6 +47,7 @@
46
47
  @import './components/sample-hierarchy-tree.css';
47
48
  @import './components/protocol-step-editor.css';
48
49
  @import './components/segmented-control.css';
50
+ @import './components/sequence-progress-bar.css';
49
51
  @import './components/multi-select.css';
50
52
  @import './components/pill.css';
51
53
  @import './components/dropdown-button.css';
@@ -54,6 +56,7 @@
54
56
  @import './components/well-edit-popup.css';
55
57
  @import './components/rack-editor.css';
56
58
  @import './components/loading-spinner.css';
59
+ @import './components/lcms-sequence-table.css';
57
60
  @import './components/divider.css';
58
61
  @import './components/status-indicator.css';
59
62
  @import './components/progress-bar.css';
@@ -14,6 +14,7 @@ import type {
14
14
  CreateBioTemplatePackCollectionOptions,
15
15
  CreateElisaAssayCollectionOptions,
16
16
  CreateLcmsBatchCollectionOptions,
17
+ CreateTargetedMetabolomicsCollectionOptions,
17
18
  CreateAssayMatrixTemplateOptions,
18
19
  CreateCalibrationCurveTemplateOptions,
19
20
  CreateDoseResponseTemplateOptions,
@@ -38,6 +39,7 @@ import type {
38
39
  FlowPanelMarker,
39
40
  InstrumentMethod,
40
41
  InstrumentRunItem,
42
+ InstrumentRunItemInput,
41
43
  InstrumentRunTemplate,
42
44
  InstrumentRunTemplateData,
43
45
  PlateMapTemplate,
@@ -858,6 +860,67 @@ export function createLcmsBatchCollection(
858
860
  )
859
861
  }
860
862
 
863
+ export function createTargetedMetabolomicsCollection(
864
+ options: CreateTargetedMetabolomicsCollectionOptions = {}
865
+ ): TemplateCollectionEnvelope {
866
+ const sampleRecords = presetSampleRecords(options.samples ?? ['S001', 'S002'])
867
+ const responseUnit = options.responseUnit ?? 'peak area ratio'
868
+ const concentrationUnit = options.concentrationUnit ?? 'uM'
869
+ const metaboliteFeatures = normalizeMetaboliteFeatures(
870
+ options.metabolites ?? ['Glucose', 'Lactate', 'Pyruvate'],
871
+ responseUnit,
872
+ )
873
+ const sampleSheet = createSampleSheetTemplate({ samples: sampleRecords })
874
+ const samplePrep = createSamplePrepTemplate({
875
+ samples: sampleRecords.map(sample => sample.sampleId),
876
+ prepType: options.prepType ?? 'extraction',
877
+ protocolName: 'Metabolomics sample preparation',
878
+ outputVolume: options.outputVolume ?? 50,
879
+ metadata: { assay: 'targeted-metabolomics' },
880
+ })
881
+ const reagents = createReagentListTemplate({
882
+ reagents: normalizeInternalStandardReagents(
883
+ options.internalStandards ?? ['Stable isotope internal standard mix'],
884
+ ),
885
+ metadata: { assay: 'targeted-metabolomics' },
886
+ })
887
+ const calibrationCurve = createCalibrationCurveTemplate({
888
+ concentrations: options.standardConcentrations ?? [0.1, 1, 10, 100],
889
+ analyte: options.calibrationAnalyte ?? firstFeatureName(metaboliteFeatures, 'Metabolite panel'),
890
+ unit: concentrationUnit,
891
+ responseUnit,
892
+ model: 'linear-weighted-1-x',
893
+ includeQc: options.includeQc,
894
+ metadata: { assay: 'targeted-metabolomics' },
895
+ })
896
+ const instrumentRun = createInstrumentRunTemplate({
897
+ items: targetedMetabolomicsRunItems(
898
+ sampleRecords,
899
+ calibrationCurve.data.points,
900
+ options.includeQc !== false,
901
+ ),
902
+ method: options.method ?? 'Targeted metabolomics',
903
+ instrument: options.instrument ?? 'LC-MS',
904
+ includeBlanks: false,
905
+ includeQc: false,
906
+ metadata: { assay: 'targeted-metabolomics' },
907
+ })
908
+ const assayMatrix = createAssayMatrixTemplate({
909
+ samples: sampleRecords.map(sample => ({
910
+ sampleId: sample.sampleId,
911
+ name: sample.name,
912
+ group: sample.group,
913
+ metadata: sample.metadata ?? {},
914
+ })),
915
+ features: metaboliteFeatures,
916
+ metadata: { assay: 'targeted-metabolomics', responseUnit },
917
+ })
918
+ return createTemplateCollection(
919
+ [sampleSheet, samplePrep, reagents, instrumentRun, calibrationCurve, assayMatrix],
920
+ { preset: 'targeted-metabolomics', ...(options.metadata ?? {}) }
921
+ )
922
+ }
923
+
861
924
  export function createElisaAssayCollection(
862
925
  options: CreateElisaAssayCollectionOptions = {}
863
926
  ): TemplateCollectionEnvelope {
@@ -1009,6 +1072,10 @@ export function createBioTemplatePresetCollection(
1009
1072
  name: 'lcms-batch',
1010
1073
  options?: CreateLcmsBatchCollectionOptions
1011
1074
  ): TemplateCollectionEnvelope
1075
+ export function createBioTemplatePresetCollection(
1076
+ name: 'targeted-metabolomics',
1077
+ options?: CreateTargetedMetabolomicsCollectionOptions
1078
+ ): TemplateCollectionEnvelope
1012
1079
  export function createBioTemplatePresetCollection(
1013
1080
  name: 'elisa-assay',
1014
1081
  options?: CreateElisaAssayCollectionOptions
@@ -1027,6 +1094,7 @@ export function createBioTemplatePresetCollection(
1027
1094
  | CreateWellPlateScreenCollectionOptions
1028
1095
  | CreateQpcrExpressionCollectionOptions
1029
1096
  | CreateLcmsBatchCollectionOptions
1097
+ | CreateTargetedMetabolomicsCollectionOptions
1030
1098
  | CreateElisaAssayCollectionOptions
1031
1099
  | CreateFlowCytometryAssayCollectionOptions
1032
1100
  | CreateWesternBlotAssayCollectionOptions
@@ -1037,6 +1105,7 @@ export function createBioTemplatePresetCollection(
1037
1105
  | CreateWellPlateScreenCollectionOptions
1038
1106
  | CreateQpcrExpressionCollectionOptions
1039
1107
  | CreateLcmsBatchCollectionOptions
1108
+ | CreateTargetedMetabolomicsCollectionOptions
1040
1109
  | CreateElisaAssayCollectionOptions
1041
1110
  | CreateFlowCytometryAssayCollectionOptions
1042
1111
  | CreateWesternBlotAssayCollectionOptions = {}
@@ -1054,6 +1123,9 @@ export function createBioTemplatePresetCollection(
1054
1123
  if (preset.name === 'lcms-batch') {
1055
1124
  return createLcmsBatchCollection(options as CreateLcmsBatchCollectionOptions)
1056
1125
  }
1126
+ if (preset.name === 'targeted-metabolomics') {
1127
+ return createTargetedMetabolomicsCollection(options as CreateTargetedMetabolomicsCollectionOptions)
1128
+ }
1057
1129
  if (preset.name === 'elisa-assay') {
1058
1130
  return createElisaAssayCollection(options as CreateElisaAssayCollectionOptions)
1059
1131
  }
@@ -1080,6 +1152,10 @@ export function bioTemplatePresetControlValuesToOptions(
1080
1152
  name: 'lcms-batch',
1081
1153
  values: BioTemplateControlValues
1082
1154
  ): CreateLcmsBatchCollectionOptions
1155
+ export function bioTemplatePresetControlValuesToOptions(
1156
+ name: 'targeted-metabolomics',
1157
+ values: BioTemplateControlValues
1158
+ ): CreateTargetedMetabolomicsCollectionOptions
1083
1159
  export function bioTemplatePresetControlValuesToOptions(
1084
1160
  name: 'elisa-assay',
1085
1161
  values: BioTemplateControlValues
@@ -1099,6 +1175,7 @@ export function bioTemplatePresetControlValuesToOptions(
1099
1175
  | CreateWellPlateScreenCollectionOptions
1100
1176
  | CreateQpcrExpressionCollectionOptions
1101
1177
  | CreateLcmsBatchCollectionOptions
1178
+ | CreateTargetedMetabolomicsCollectionOptions
1102
1179
  | CreateElisaAssayCollectionOptions
1103
1180
  | CreateFlowCytometryAssayCollectionOptions
1104
1181
  | CreateWesternBlotAssayCollectionOptions
@@ -1109,6 +1186,7 @@ export function bioTemplatePresetControlValuesToOptions(
1109
1186
  | CreateWellPlateScreenCollectionOptions
1110
1187
  | CreateQpcrExpressionCollectionOptions
1111
1188
  | CreateLcmsBatchCollectionOptions
1189
+ | CreateTargetedMetabolomicsCollectionOptions
1112
1190
  | CreateElisaAssayCollectionOptions
1113
1191
  | CreateFlowCytometryAssayCollectionOptions
1114
1192
  | CreateWesternBlotAssayCollectionOptions {
@@ -1149,6 +1227,26 @@ export function bioTemplatePresetControlValuesToOptions(
1149
1227
  }
1150
1228
  }
1151
1229
 
1230
+ if (preset.name === 'targeted-metabolomics') {
1231
+ return {
1232
+ samples: readStringList(values.sampleNames, ['S001', 'S002']),
1233
+ metabolites: readStringList(values.metaboliteNames, ['Glucose', 'Lactate', 'Pyruvate']),
1234
+ internalStandards: readStringList(
1235
+ values.internalStandards,
1236
+ ['Stable isotope internal standard mix'],
1237
+ ),
1238
+ standardConcentrations: readNumberList(values.standardConcentrations, [0.1, 1, 10, 100]),
1239
+ concentrationUnit: readString(values.concentrationUnit, 'uM'),
1240
+ responseUnit: readString(values.responseUnit, 'peak area ratio'),
1241
+ calibrationAnalyte: readOptionalString(values.calibrationAnalyte),
1242
+ instrument: readString(values.instrument, 'LC-MS'),
1243
+ method: readString(values.method, 'Targeted metabolomics'),
1244
+ prepType: readString(values.prepType, 'extraction') as CreateSamplePrepTemplateOptions['prepType'],
1245
+ outputVolume: readNumber(values.outputVolume, 50),
1246
+ includeQc: readBoolean(values.includeQc, true),
1247
+ }
1248
+ }
1249
+
1152
1250
  if (preset.name === 'elisa-assay') {
1153
1251
  return {
1154
1252
  samples: readStringList(values.sampleNames, ['Control', 'Treatment']),
@@ -1194,6 +1292,10 @@ export function createBioTemplatePresetCollectionFromControls(
1194
1292
  name: 'lcms-batch',
1195
1293
  values: BioTemplateControlValues
1196
1294
  ): TemplateCollectionEnvelope
1295
+ export function createBioTemplatePresetCollectionFromControls(
1296
+ name: 'targeted-metabolomics',
1297
+ values: BioTemplateControlValues
1298
+ ): TemplateCollectionEnvelope
1197
1299
  export function createBioTemplatePresetCollectionFromControls(
1198
1300
  name: 'elisa-assay',
1199
1301
  values: BioTemplateControlValues
@@ -1863,6 +1965,96 @@ function normalizeReagent(reagent: ReagentTemplateInput): ReagentRecord {
1863
1965
  }
1864
1966
  }
1865
1967
 
1968
+ function normalizeMetaboliteFeatures(
1969
+ metabolites: Array<string | AssayFeature>,
1970
+ responseUnit: string,
1971
+ ): AssayFeature[] {
1972
+ return metabolites.map((metabolite, index) => {
1973
+ if (typeof metabolite === 'string') {
1974
+ return {
1975
+ id: idFromName(metabolite, `metabolite-${index + 1}`),
1976
+ name: metabolite,
1977
+ type: 'metabolite',
1978
+ unit: responseUnit,
1979
+ metadata: { quantitation: 'targeted' },
1980
+ }
1981
+ }
1982
+
1983
+ const feature = normalizeAssayFeature(metabolite)
1984
+ return {
1985
+ ...feature,
1986
+ type: feature.type ?? 'metabolite',
1987
+ unit: feature.unit ?? responseUnit,
1988
+ metadata: {
1989
+ quantitation: 'targeted',
1990
+ ...feature.metadata,
1991
+ },
1992
+ }
1993
+ })
1994
+ }
1995
+
1996
+ function normalizeInternalStandardReagents(
1997
+ internalStandards: Array<string | ReagentTemplateInput>,
1998
+ ): ReagentTemplateInput[] {
1999
+ return internalStandards.map((standard, index) => {
2000
+ if (typeof standard === 'string') {
2001
+ return {
2002
+ id: idFromName(standard, `internal-standard-${index + 1}`),
2003
+ name: standard,
2004
+ kind: 'compound',
2005
+ metadata: { role: 'internal-standard' },
2006
+ }
2007
+ }
2008
+
2009
+ const reagent = normalizeReagent(standard)
2010
+ return {
2011
+ ...reagent,
2012
+ kind: standard.kind ?? 'compound',
2013
+ metadata: {
2014
+ role: 'internal-standard',
2015
+ ...reagent.metadata,
2016
+ },
2017
+ }
2018
+ })
2019
+ }
2020
+
2021
+ function targetedMetabolomicsRunItems(
2022
+ samples: SampleRecord[],
2023
+ calibrationPoints: CalibrationPoint[],
2024
+ includeQc: boolean,
2025
+ ): InstrumentRunItemInput[] {
2026
+ const calibrationItems = calibrationPoints
2027
+ .filter(point => point.role !== 'qc' || includeQc)
2028
+ .filter(point => point.role === 'blank' || point.role === 'standard' || point.role === 'qc')
2029
+ .map((point): InstrumentRunItemInput => ({
2030
+ id: `${point.id}-run`,
2031
+ kind: calibrationRunItemKind(point.role),
2032
+ name: point.level,
2033
+ metadata: {
2034
+ concentration: point.concentration,
2035
+ unit: point.unit,
2036
+ role: point.role,
2037
+ },
2038
+ }))
2039
+
2040
+ const sampleItems = samples.map((sample): InstrumentRunItemInput => ({
2041
+ sampleId: sample.sampleId,
2042
+ name: sample.name ?? sample.sampleId,
2043
+ kind: 'sample',
2044
+ }))
2045
+
2046
+ return [...calibrationItems, ...sampleItems]
2047
+ }
2048
+
2049
+ function calibrationRunItemKind(role: CalibrationPoint['role']): 'blank' | 'standard' | 'qc' {
2050
+ if (role === 'blank' || role === 'standard' || role === 'qc') return role
2051
+ return 'standard'
2052
+ }
2053
+
2054
+ function firstFeatureName(features: AssayFeature[], fallback: string): string {
2055
+ return features.find(feature => feature.name)?.name ?? fallback
2056
+ }
2057
+
1866
2058
  function normalizeDateString(value: Date | string | null | undefined): string | undefined {
1867
2059
  if (value === undefined || value === null) return undefined
1868
2060
  return value instanceof Date ? value.toISOString() : value
@@ -2114,6 +2306,15 @@ function readInteger(value: unknown, fallback: number): number {
2114
2306
  return Number.isFinite(parsed) ? Math.max(1, Math.round(parsed)) : fallback
2115
2307
  }
2116
2308
 
2309
+ function readNumber(value: unknown, fallback: number): number {
2310
+ const parsed = typeof value === 'number'
2311
+ ? value
2312
+ : typeof value === 'string'
2313
+ ? Number(value)
2314
+ : Number.NaN
2315
+ return Number.isFinite(parsed) ? parsed : fallback
2316
+ }
2317
+
2117
2318
  function readBoolean(value: unknown, fallback: boolean): boolean {
2118
2319
  return typeof value === 'boolean' ? value : fallback
2119
2320
  }
@@ -412,6 +412,74 @@ const presetControlSchemas = {
412
412
  ...templateControlSchemas['instrument-run'],
413
413
  ...templateControlSchemas['assay-matrix'],
414
414
  },
415
+ 'targeted-metabolomics': {
416
+ sampleNames: {
417
+ type: 'tags',
418
+ label: 'Samples',
419
+ default: ['S001', 'S002'],
420
+ section: 'samples',
421
+ sectionLabel: 'Samples',
422
+ sectionSubtitle: 'Unknowns and QC pool',
423
+ view: 'run',
424
+ sidebar: { icon: ICONS.samples, iconColor: '#10b981', iconBg: '#d1fae5' },
425
+ },
426
+ metaboliteNames: {
427
+ type: 'tags',
428
+ label: 'Metabolites',
429
+ default: ['Glucose', 'Lactate', 'Pyruvate'],
430
+ section: 'features',
431
+ sectionLabel: 'Metabolites',
432
+ sectionSubtitle: 'Quantitation panel',
433
+ view: 'analysis',
434
+ sidebar: { icon: ICONS.table, iconColor: '#0ea5e9', iconBg: '#e0f2fe' },
435
+ },
436
+ internalStandards: {
437
+ type: 'tags',
438
+ label: 'Internal standards',
439
+ default: ['Stable isotope internal standard mix'],
440
+ section: 'standards',
441
+ sectionLabel: 'Standards',
442
+ sectionSubtitle: 'Calibration and internal standards',
443
+ view: 'analysis',
444
+ sidebar: { icon: ICONS.dose, iconColor: '#8b5cf6', iconBg: '#ede9fe' },
445
+ },
446
+ standardConcentrations: {
447
+ type: 'tags',
448
+ label: 'Calibration levels',
449
+ default: ['0.1', '1', '10', '100'],
450
+ section: 'standards',
451
+ view: 'analysis',
452
+ },
453
+ concentrationUnit: {
454
+ label: 'Concentration unit',
455
+ default: 'uM',
456
+ options: CONCENTRATION_UNITS,
457
+ section: 'standards',
458
+ view: 'analysis',
459
+ },
460
+ responseUnit: {
461
+ label: 'Response unit',
462
+ default: 'peak area ratio',
463
+ section: 'readout',
464
+ sectionLabel: 'Readout',
465
+ view: 'analysis',
466
+ sidebar: { icon: ICONS.table, iconColor: '#0ea5e9', iconBg: '#e0f2fe' },
467
+ },
468
+ instrument: {
469
+ ...templateControlSchemas['instrument-run'].instrument,
470
+ default: 'LC-MS',
471
+ },
472
+ method: {
473
+ ...templateControlSchemas['instrument-run'].method,
474
+ default: 'Targeted metabolomics',
475
+ },
476
+ prepType: {
477
+ ...templateControlSchemas['sample-prep'].prepType,
478
+ default: 'extraction',
479
+ },
480
+ outputVolume: templateControlSchemas['sample-prep'].outputVolume,
481
+ includeQc: templateControlSchemas['instrument-run'].includeQc,
482
+ },
415
483
  'elisa-assay': {
416
484
  sampleNames: {
417
485
  type: 'tags',
@@ -20,6 +20,7 @@ export {
20
20
  createReagentListTemplate,
21
21
  createSampleSheetTemplate,
22
22
  createSamplePrepTemplate,
23
+ createTargetedMetabolomicsCollection,
23
24
  createTemplateCollection,
24
25
  createDefaultBioTemplate,
25
26
  createTimeCourseTemplate,
@@ -186,6 +187,7 @@ export type {
186
187
  CreateFlowCytometryPanelTemplateOptions,
187
188
  CreateInstrumentRunTemplateOptions,
188
189
  CreateLcmsBatchCollectionOptions,
190
+ CreateTargetedMetabolomicsCollectionOptions,
189
191
  CreatePlateMapTemplateOptions,
190
192
  CreateProtocolStepsTemplateOptions,
191
193
  CreateQpcrExpressionCollectionOptions,
@@ -74,6 +74,29 @@ export const bioTemplatePresets = [
74
74
  frontend_adapters: ['toTemplateDataFrame', 'toInstrumentRunDataFrame', 'toInstrumentRunSteps', 'toInstrumentRunScheduleEvents', 'toAssayMatrixDataFrame', 'toAssayMatrixSampleOptions'],
75
75
  components: ['DataFrame', 'ExperimentTimeline', 'ScheduleCalendar', 'ChartContainer', 'SampleSelector'],
76
76
  },
77
+ {
78
+ name: 'targeted-metabolomics',
79
+ label: 'Targeted metabolomics',
80
+ category: 'bio-template-preset',
81
+ purpose: 'Sample metadata, extraction prep, internal standards, LC/GC-MS sequence, calibration curve, and metabolite readout matrix for quantitative metabolomics.',
82
+ templates: ['sample-sheet', 'sample-prep', 'reagent-list', 'instrument-run', 'calibration-curve', 'assay-matrix'],
83
+ aliases: [
84
+ 'targeted metabolomics',
85
+ 'quantitative metabolomics',
86
+ 'lc-ms metabolomics',
87
+ 'gc-ms metabolomics',
88
+ 'targeted lcms',
89
+ 'metabolite quantitation',
90
+ 'mass spec quantitation',
91
+ 'internal standards',
92
+ 'peak area ratio',
93
+ ],
94
+ python_import: 'from mint_sdk.templates import create_targeted_metabolomics_collection, save_template_collection',
95
+ python_example: 'create_targeted_metabolomics_collection(samples=["S001", "S002"], metabolites=["Glucose", "Lactate"], internal_standards=["13C Glucose"], instrument="LC-MS")',
96
+ frontend_import: "import { createTargetedMetabolomicsCollection, toInstrumentRunDataFrame, toCalibrationCurveDataFrame, toAssayMatrixDataFrame, toTemplateDataFrame } from '@morscherlab/mint-sdk/templates'",
97
+ frontend_adapters: ['toTemplateDataFrame', 'toSamplePrepDataFrame', 'toReagentDataFrame', 'toInstrumentRunDataFrame', 'toInstrumentRunSteps', 'toInstrumentRunScheduleEvents', 'toCalibrationCurveDataFrame', 'toAssayMatrixDataFrame', 'toAssayMatrixSampleOptions'],
98
+ components: ['DataFrame', 'ReagentList', 'ExperimentTimeline', 'ScheduleCalendar', 'FitPanel', 'ChartContainer', 'SampleSelector'],
99
+ },
77
100
  {
78
101
  name: 'elisa-assay',
79
102
  label: 'ELISA assay',
@@ -31,6 +31,7 @@ export type TemplatePresetId =
31
31
  | 'wellplate-screen'
32
32
  | 'qpcr-expression'
33
33
  | 'lcms-batch'
34
+ | 'targeted-metabolomics'
34
35
  | 'elisa-assay'
35
36
  | 'flow-cytometry-assay'
36
37
  | 'western-blot-assay'
@@ -596,6 +597,22 @@ export interface CreateLcmsBatchCollectionOptions {
596
597
  metadata?: Record<string, unknown>
597
598
  }
598
599
 
600
+ export interface CreateTargetedMetabolomicsCollectionOptions {
601
+ samples?: Array<string | SampleRecord>
602
+ metabolites?: Array<string | AssayFeature>
603
+ internalStandards?: Array<string | ReagentTemplateInput>
604
+ standardConcentrations?: number[]
605
+ concentrationUnit?: string
606
+ responseUnit?: string
607
+ calibrationAnalyte?: string
608
+ instrument?: string
609
+ method?: string | InstrumentMethodInput
610
+ prepType?: SamplePrepStepType
611
+ outputVolume?: number
612
+ includeQc?: boolean
613
+ metadata?: Record<string, unknown>
614
+ }
615
+
599
616
  export interface CreateElisaAssayCollectionOptions {
600
617
  samples?: Array<string | SampleRecord>
601
618
  analyte?: string
package/src/types/auth.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { RoleInfo } from '../permissions'
2
+
1
3
  // Authentication configuration
2
4
  export interface AuthConfig {
3
5
  authRequired: boolean
@@ -14,6 +16,7 @@ export interface UserInfo {
14
16
  shortname: string | null
15
17
  email: string | null
16
18
  role: string
19
+ roleObj?: RoleInfo | null
17
20
  isActive: boolean
18
21
  }
19
22