@morscherlab/mint-sdk 1.0.0-beta.4 → 1.0.0-beta.6

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 (58) hide show
  1. package/dist/components/AppSidebar.vue.d.ts +1 -1
  2. package/dist/components/index.js +2 -2
  3. package/dist/{components-BkGF4B4y.js → components-DihbSJjU.js} +2524 -2517
  4. package/dist/components-DihbSJjU.js.map +1 -0
  5. package/dist/composables/index.js +3 -3
  6. package/dist/{composables-CHsME9H1.js → composables-BcgZ6diz.js} +2 -2
  7. package/dist/{composables-CHsME9H1.js.map → composables-BcgZ6diz.js.map} +1 -1
  8. package/dist/index.js +5 -5
  9. package/dist/install.js +2 -2
  10. package/dist/styles.css +738 -738
  11. package/dist/templates/adapters.d.ts +7 -1
  12. package/dist/templates/catalog.d.ts +5 -5
  13. package/dist/templates/index.d.ts +2 -2
  14. package/dist/templates/index.js +2 -2
  15. package/dist/templates/presets.d.ts +4 -4
  16. package/dist/templates/types.d.ts +4 -1
  17. package/dist/{templates-B5jmTWuk.js → templates-Cyt0Suwf.js} +213 -19
  18. package/dist/{templates-B5jmTWuk.js.map → templates-Cyt0Suwf.js.map} +1 -1
  19. package/dist/{useScheduleDrag-BgzpQT53.js → useExperimentData-CM6Y0u5L.js} +183 -183
  20. package/dist/useExperimentData-CM6Y0u5L.js.map +1 -0
  21. package/package.json +1 -1
  22. package/src/__tests__/components/AppSidebar.test.ts +4 -2
  23. package/src/__tests__/components/AppTopBar.test.ts +9 -3
  24. package/src/__tests__/components/{AppPageSelector.test.ts → AppTopBarPageSelector.test.ts} +8 -8
  25. package/src/__tests__/components/{AppPillNav.test.ts → AppTopBarPillNav.test.ts} +7 -7
  26. package/src/__tests__/components/BioTemplatePackWorkspaceView.test.ts +17 -0
  27. package/src/__tests__/components/BioTemplatePresetWorkspaceView.test.ts +22 -0
  28. package/src/__tests__/components/BioTemplateRenderer.test.ts +25 -0
  29. package/src/__tests__/components/ComponentBindingRenderer.test.ts +117 -0
  30. package/src/__tests__/composables/useBioTemplatePackWorkspace.test.ts +1 -1
  31. package/src/__tests__/composables/useBioTemplatePresetWorkspace.test.ts +1 -1
  32. package/src/__tests__/composables/useControlSchema.test.ts +1 -1
  33. package/src/__tests__/templates/templates.test.ts +44 -0
  34. package/src/components/AppSidebar.vue +3 -3
  35. package/src/components/AppTopBar.vue +7 -7
  36. package/src/components/BioTemplatePresetWorkspaceView.vue +3 -3
  37. package/src/components/BioTemplateRenderer.story.vue +2 -2
  38. package/src/components/ComponentBindingRenderer.story.vue +30 -0
  39. package/src/components/ComponentBindingRenderer.vue +9 -0
  40. package/src/components/ExperimentPopover.story.vue +2 -2
  41. package/src/styles/components/app-page-selector.css +1 -1
  42. package/src/styles/components/app-pill-nav.css +1 -1
  43. package/src/styles/components/experiment-popover.css +2 -2
  44. package/src/templates/adapters.ts +193 -0
  45. package/src/templates/catalog.ts +5 -5
  46. package/src/templates/componentBindings.ts +52 -3
  47. package/src/templates/index.ts +6 -0
  48. package/src/templates/packs.ts +10 -1
  49. package/src/templates/presets.ts +14 -4
  50. package/src/templates/types.ts +4 -0
  51. package/dist/components-BkGF4B4y.js.map +0 -1
  52. package/dist/useScheduleDrag-BgzpQT53.js.map +0 -1
  53. /package/dist/__tests__/components/{AppPageSelector.test.d.ts → AppTopBarPageSelector.test.d.ts} +0 -0
  54. /package/dist/__tests__/components/{AppPillNav.test.d.ts → AppTopBarPillNav.test.d.ts} +0 -0
  55. /package/dist/components/internal/{AppPageSelectorInternal.vue.d.ts → AppTopBarPageSelectorInternal.vue.d.ts} +0 -0
  56. /package/dist/components/internal/{AppPillNavInternal.vue.d.ts → AppTopBarPillNavInternal.vue.d.ts} +0 -0
  57. /package/src/components/internal/{AppPageSelectorInternal.vue → AppTopBarPageSelectorInternal.vue} +0 -0
  58. /package/src/components/internal/{AppPillNavInternal.vue → AppTopBarPillNavInternal.vue} +0 -0
@@ -44,11 +44,11 @@ const lcmsBatch = createLcmsBatchCollection({
44
44
  </div>
45
45
  </Variant>
46
46
 
47
- <Variant title="LCMS Batch Tables">
47
+ <Variant title="LC-MS Batch Preview">
48
48
  <div style="padding: 2rem; background: var(--bg-primary, #f8fafc);">
49
49
  <BioTemplateRenderer
50
50
  :target="lcmsBatch"
51
- :include="['DataFrame']"
51
+ :include="['DataFrame', 'ExperimentTimeline', 'ScheduleCalendar']"
52
52
  dense
53
53
  />
54
54
  </div>
@@ -5,6 +5,10 @@ import {
5
5
  defineDoseDesignControlModel,
6
6
  useControlWorkspace,
7
7
  } from '../composables/useControlSchema'
8
+ import {
9
+ createLcmsBatchCollection,
10
+ toBioTemplateComponentBindings,
11
+ } from '../templates'
8
12
 
9
13
  const model = defineDoseDesignControlModel({
10
14
  selectedWells: ['A1', 'A2', 'B1'],
@@ -12,6 +16,18 @@ const model = defineDoseDesignControlModel({
12
16
  includeMolecularWeight: true,
13
17
  })
14
18
  const workspace = useControlWorkspace(model)
19
+ const lcmsCollection = createLcmsBatchCollection({
20
+ samples: [
21
+ { sampleId: 'blank-1', name: 'Blank', group: 'Blank' },
22
+ { sampleId: 'qc-pool', name: 'Pooled QC', group: 'QC' },
23
+ { sampleId: 's001', name: 'Patient 001', group: 'Case' },
24
+ { sampleId: 's002', name: 'Patient 002', group: 'Control' },
25
+ ],
26
+ features: ['Glucose', 'Lactate', 'Citrate'],
27
+ instrument: 'Orbitrap LC-MS',
28
+ method: 'HILIC negative',
29
+ includeQc: true,
30
+ })
15
31
 
16
32
  const state = ref({
17
33
  dense: false,
@@ -22,6 +38,7 @@ const state = ref({
22
38
  })
23
39
 
24
40
  const bindings = computed(() => workspace.componentBindings.value)
41
+ const lcmsBindings = computed(() => toBioTemplateComponentBindings(lcmsCollection))
25
42
  </script>
26
43
 
27
44
  <template>
@@ -39,6 +56,19 @@ const bindings = computed(() => workspace.componentBindings.value)
39
56
  </div>
40
57
  </Variant>
41
58
 
59
+ <Variant title="LC-MS Batch Bindings">
60
+ <div style="padding: 2rem; min-height: 640px; background: var(--bg-secondary, #f8fafc);">
61
+ <ComponentBindingRenderer
62
+ :bindings="lcmsBindings"
63
+ :dense="state.dense"
64
+ :readonly="state.readonly"
65
+ :show-headers="state.showHeaders"
66
+ :show-descriptions="state.showDescriptions"
67
+ :layout="state.layout"
68
+ />
69
+ </div>
70
+ </Variant>
71
+
42
72
  <template #controls>
43
73
  <HstCheckbox v-model="state.dense" title="Dense" />
44
74
  <HstCheckbox v-model="state.readonly" title="Readonly" />
@@ -7,6 +7,7 @@ import ExperimentTimeline from './ExperimentTimeline.vue'
7
7
  import PlateMapEditor from './PlateMapEditor.vue'
8
8
  import ReagentList from './ReagentList.vue'
9
9
  import SampleSelector from './SampleSelector.vue'
10
+ import ScheduleCalendar from './ScheduleCalendar.vue'
10
11
  import WellPlate from './WellPlate.vue'
11
12
 
12
13
  export interface ComponentBindingRendererBinding {
@@ -69,6 +70,7 @@ const componentRegistry: Record<string, Component> = {
69
70
  PlateMapEditor,
70
71
  ReagentList,
71
72
  SampleSelector,
73
+ ScheduleCalendar,
72
74
  WellPlate,
73
75
  }
74
76
 
@@ -152,6 +154,13 @@ function normalizeProps(binding: ComponentBindingRendererBinding): Record<string
152
154
  enableGrouping: props.readonly ? false : (base.enableGrouping ?? true),
153
155
  enableSmartGroup: props.readonly ? false : (base.enableSmartGroup ?? true),
154
156
  }
157
+ case 'ScheduleCalendar':
158
+ return {
159
+ ...base,
160
+ readonly: props.readonly || Boolean(base.readonly),
161
+ showNavigation: props.dense ? false : (base.showNavigation ?? true),
162
+ showViewToggle: props.dense ? false : (base.showViewToggle ?? true),
163
+ }
155
164
  default:
156
165
  return base
157
166
  }
@@ -283,7 +283,7 @@ const STATUSES = ['planned', 'ongoing', 'completed', 'ready_to_extract', 'proces
283
283
  </div>
284
284
  </div>
285
285
  <p style="margin-top: 1rem; font-size: 0.75rem; color: var(--text-muted); text-align: center;">
286
- Same 2.375rem height and refresh-design motion as AppPluginSwitcher, AppPageSelector, AppAvatarMenu.
286
+ Same 2.375rem height and refresh-design motion as AppPluginSwitcher, AppTopBar page selector, AppAvatarMenu.
287
287
  </p>
288
288
  </div>
289
289
  </Variant>
@@ -363,7 +363,7 @@ optionally pairs with an inline Save button as a split unit.
363
363
 
364
364
  ### Refresh-design alignment
365
365
 
366
- - **Height** `2.375rem` — matches `AppPluginSwitcher` / `AppPageSelector` so the
366
+ - **Height** `2.375rem` — matches `AppPluginSwitcher` / `AppTopBar page selector` so the
367
367
  topbar's right cluster reads at one optical baseline.
368
368
  - **Surface** `--bg-card` + `--border-color` — consistent with the white-card +
369
369
  border language.
@@ -1,4 +1,4 @@
1
- /* AppPageSelector — left-side page dropdown */
1
+ /* AppTopBar page selector — left-side route/page dropdown */
2
2
 
3
3
  .mint-page-selector {
4
4
  position: relative;
@@ -1,4 +1,4 @@
1
- /* AppPillNav — centered top-level navigation pills */
1
+ /* AppTopBar pillNav — centered top-level navigation pills */
2
2
 
3
3
  .mint-pill-nav {
4
4
  display: inline-flex;
@@ -1,4 +1,4 @@
1
- /* ExperimentPopover — aligned with refresh design (AppPluginSwitcher / AppPageSelector) */
1
+ /* ExperimentPopover — aligned with refresh design (AppPluginSwitcher / AppTopBar page selector) */
2
2
 
3
3
  /* Container */
4
4
  .mint-experiment-popover {
@@ -65,7 +65,7 @@
65
65
  transition-duration: 0.05s;
66
66
  }
67
67
 
68
- /* Active (panel open) — ring treatment, same language as PluginSwitcher/PageSelector */
68
+ /* Active (panel open) — ring treatment, same language as PluginSwitcher/page selector */
69
69
  .mint-experiment-popover__trigger--active {
70
70
  border-color: var(--color-primary);
71
71
  box-shadow: 0 0 0 3px var(--color-primary-soft);
@@ -2,6 +2,11 @@ import type {
2
2
  DataFrameColumn,
3
3
  PlateCondition,
4
4
  PlateMapEditorState,
5
+ ProtocolStep,
6
+ ProtocolStepStatus,
7
+ ProtocolStepType,
8
+ ScheduleEvent,
9
+ ScheduleEventStatus,
5
10
  SelectOption,
6
11
  Well,
7
12
  } from '../types'
@@ -24,6 +29,7 @@ import type {
24
29
  AssayMatrixColumnsAdapterResult,
25
30
  AssayMatrixDataFrameAdapterResult,
26
31
  AssayMatrixRowsAdapterResult,
32
+ AssayMatrixSampleOptionsAdapterResult,
27
33
  AssayMatrixTemplate,
28
34
  AssayMatrixTemplateData,
29
35
  BioTemplateEnvelope,
@@ -44,8 +50,13 @@ import type {
44
50
  InstrumentRunColumnsAdapterResult,
45
51
  InstrumentRunDataFrameAdapterResult,
46
52
  InstrumentRunRowsAdapterResult,
53
+ InstrumentRunScheduleEventsAdapterResult,
54
+ InstrumentRunStepsAdapterResult,
47
55
  InstrumentRunTemplate,
48
56
  InstrumentRunTemplateData,
57
+ InstrumentRunItem,
58
+ InstrumentRunItemKind,
59
+ InstrumentRunStatus,
49
60
  PlateMapEditorAdapterResult,
50
61
  PlateMapTemplate,
51
62
  PlateMapTemplateData,
@@ -478,6 +489,19 @@ export function toAssayMatrixDataFrame(
478
489
  }
479
490
  }
480
491
 
492
+ /** Convert an assay-matrix template into SampleSelector options derived from assay samples. */
493
+ export function toAssayMatrixSampleOptions(
494
+ template: AssayMatrixTemplate | AssayMatrixTemplateData
495
+ ): AssayMatrixSampleOptionsAdapterResult {
496
+ const data = getTemplateData(template, 'assay-matrix')
497
+ validateAssayMatrixData(data)
498
+ return data.samples.map((sample): SelectOption<string> => ({
499
+ value: sample.sampleId,
500
+ label: sample.name ?? sample.sampleId,
501
+ description: sample.group,
502
+ }))
503
+ }
504
+
481
505
  export function toReagentListItems(
482
506
  template: ReagentListTemplate | ReagentListTemplateData
483
507
  ): ReagentListItemsAdapterResult {
@@ -649,6 +673,91 @@ export function toInstrumentRunDataFrame(
649
673
  }
650
674
  }
651
675
 
676
+ /** Convert an instrument-run template into ExperimentTimeline protocol steps for acquisition queue previews. */
677
+ export function toInstrumentRunSteps(
678
+ template: InstrumentRunTemplate | InstrumentRunTemplateData
679
+ ): InstrumentRunStepsAdapterResult {
680
+ const data = getTemplateData(template, 'instrument-run')
681
+ validateInstrumentRunData(data)
682
+ const methods = new Map(data.methods.map(method => [method.id, method]))
683
+ return [...data.items]
684
+ .sort((a, b) => a.order - b.order)
685
+ .map((item): ProtocolStep => {
686
+ const method = methods.get(item.methodId)
687
+ const name = item.name ?? item.sampleId ?? item.kind
688
+ return {
689
+ id: item.id,
690
+ type: instrumentRunStepType(item.kind),
691
+ name,
692
+ description: [
693
+ item.kind,
694
+ method?.name,
695
+ item.vial ? `Vial ${item.vial}` : undefined,
696
+ item.wellId ? `Well ${item.wellId}` : undefined,
697
+ ]
698
+ .filter(Boolean)
699
+ .join(' / '),
700
+ duration: item.expectedDurationMin,
701
+ status: instrumentRunStepStatus(item.status),
702
+ parameters: {
703
+ kind: item.kind,
704
+ sampleId: item.sampleId,
705
+ methodId: item.methodId,
706
+ method: method?.name,
707
+ vial: item.vial,
708
+ plateId: item.plateId,
709
+ wellId: item.wellId,
710
+ injectionVolume: item.injectionVolume,
711
+ instrument: data.instrument ?? method?.instrument,
712
+ },
713
+ order: item.order,
714
+ }
715
+ })
716
+ }
717
+
718
+ /** Convert an instrument-run template into readonly ScheduleCalendar events ordered by run queue timing. */
719
+ export function toInstrumentRunScheduleEvents(
720
+ template: InstrumentRunTemplate | InstrumentRunTemplateData
721
+ ): InstrumentRunScheduleEventsAdapterResult {
722
+ const data = getTemplateData(template, 'instrument-run')
723
+ validateInstrumentRunData(data)
724
+ const methods = new Map(data.methods.map(method => [method.id, method]))
725
+ let cursor = dateFromMetadata(data.metadata, ['scheduledStart', 'runStart', 'start', 'startedAt'])
726
+ ?? new Date('2024-01-01T08:00:00.000Z')
727
+
728
+ return [...data.items]
729
+ .sort((a, b) => a.order - b.order)
730
+ .map((item): ScheduleEvent => {
731
+ const method = methods.get(item.methodId)
732
+ const start = dateFromMetadata(item.metadata, ['scheduledStart', 'start', 'startedAt']) ?? cursor
733
+ const end = dateFromMetadata(item.metadata, ['scheduledEnd', 'end', 'completedAt'])
734
+ ?? addMinutes(start, Math.max(item.expectedDurationMin ?? 10, 1))
735
+ cursor = end
736
+
737
+ return {
738
+ id: item.id,
739
+ title: item.name ?? item.sampleId ?? item.kind,
740
+ start: start.toISOString(),
741
+ end: end.toISOString(),
742
+ color: instrumentRunScheduleColor(item.kind),
743
+ status: instrumentRunScheduleStatus(item.status),
744
+ draggable: false,
745
+ resizable: false,
746
+ metadata: {
747
+ kind: item.kind,
748
+ sampleId: item.sampleId,
749
+ methodId: item.methodId,
750
+ method: method?.name,
751
+ vial: item.vial,
752
+ plateId: item.plateId,
753
+ wellId: item.wellId,
754
+ injectionVolume: item.injectionVolume,
755
+ instrument: data.instrument ?? method?.instrument,
756
+ },
757
+ }
758
+ })
759
+ }
760
+
652
761
  export function toQpcrRows(
653
762
  template: QpcrPlateTemplate | QpcrPlateTemplateData
654
763
  ): QpcrRowsAdapterResult {
@@ -783,3 +892,87 @@ function sampleColumnKey(columnId: string): string {
783
892
  }
784
893
  return aliases[columnId] ?? columnId
785
894
  }
895
+
896
+ function instrumentRunStepType(kind: InstrumentRunItemKind): ProtocolStepType {
897
+ switch (kind) {
898
+ case 'wash':
899
+ return 'wash'
900
+ case 'blank':
901
+ case 'qc':
902
+ case 'standard':
903
+ case 'calibration':
904
+ case 'sample':
905
+ return 'measurement'
906
+ default:
907
+ return 'custom'
908
+ }
909
+ }
910
+
911
+ function instrumentRunStepStatus(status: InstrumentRunStatus): ProtocolStepStatus {
912
+ switch (status) {
913
+ case 'running':
914
+ return 'in_progress'
915
+ case 'completed':
916
+ return 'completed'
917
+ case 'failed':
918
+ return 'failed'
919
+ case 'skipped':
920
+ return 'skipped'
921
+ case 'planned':
922
+ case 'queued':
923
+ default:
924
+ return 'pending'
925
+ }
926
+ }
927
+
928
+ function instrumentRunScheduleStatus(status: InstrumentRunStatus): ScheduleEventStatus {
929
+ switch (status) {
930
+ case 'running':
931
+ return 'in-progress'
932
+ case 'completed':
933
+ return 'confirmed'
934
+ case 'failed':
935
+ case 'skipped':
936
+ return 'cancelled'
937
+ case 'planned':
938
+ case 'queued':
939
+ default:
940
+ return 'pending'
941
+ }
942
+ }
943
+
944
+ function instrumentRunScheduleColor(kind: InstrumentRunItem['kind']): string {
945
+ switch (kind) {
946
+ case 'blank':
947
+ return '#64748b'
948
+ case 'qc':
949
+ return '#8b5cf6'
950
+ case 'standard':
951
+ case 'calibration':
952
+ return '#f59e0b'
953
+ case 'wash':
954
+ return '#06b6d4'
955
+ case 'sample':
956
+ return '#2563eb'
957
+ default:
958
+ return '#475569'
959
+ }
960
+ }
961
+
962
+ function dateFromMetadata(
963
+ metadata: Record<string, unknown> | undefined,
964
+ keys: readonly string[]
965
+ ): Date | undefined {
966
+ if (!metadata) return undefined
967
+ for (const key of keys) {
968
+ const value = metadata[key]
969
+ if (!(typeof value === 'string' || value instanceof Date)) continue
970
+ const date = new Date(value)
971
+ if (!Number.isNaN(date.getTime())) return date
972
+ }
973
+ return undefined
974
+ }
975
+
976
+ function addMinutes(date: Date, minutes: number): Date {
977
+ return new Date(date.getTime() + minutes * 60_000)
978
+ }
@@ -142,8 +142,8 @@ export const bioTemplateCatalog = [
142
142
  aliases: ['omics matrix', 'measurement matrix', 'readout matrix', 'feature table'],
143
143
  python_import: 'from mint_sdk.templates import AssayMatrixTemplate, save_template',
144
144
  python_example: 'AssayMatrixTemplate.create(samples=["S001", "S002"], features=["Lactate", "Glucose"])',
145
- frontend_import: "import { toTemplateDataFrame } from '@morscherlab/mint-sdk/templates'",
146
- frontend_adapters: ['toTemplateDataFrame', 'toAssayMatrixDataFrame', 'toAssayMatrixRows', 'toAssayMatrixColumns'],
145
+ frontend_import: "import { toAssayMatrixSampleOptions, toTemplateDataFrame } from '@morscherlab/mint-sdk/templates'",
146
+ frontend_adapters: ['toTemplateDataFrame', 'toAssayMatrixDataFrame', 'toAssayMatrixRows', 'toAssayMatrixColumns', 'toAssayMatrixSampleOptions'],
147
147
  components: ['DataFrame', 'ChartContainer', 'SampleSelector'],
148
148
  add_command: 'mint add data-template assay-matrix --page',
149
149
  },
@@ -187,9 +187,9 @@ export const bioTemplateCatalog = [
187
187
  aliases: ['run queue', 'sequence table', 'sample queue', 'lcms run', 'instrument sequence', 'batch run'],
188
188
  python_import: 'from mint_sdk.templates import InstrumentRunTemplate, save_template',
189
189
  python_example: 'InstrumentRunTemplate.create(["S001", "S002"], instrument="LC-MS")',
190
- frontend_import: "import { toInstrumentRunDataFrame, toTemplateDataFrame } from '@morscherlab/mint-sdk/templates'",
191
- frontend_adapters: ['toTemplateDataFrame', 'toInstrumentRunDataFrame', 'toInstrumentRunRows', 'toInstrumentRunColumns'],
192
- components: ['DataFrame', 'ScheduleCalendar', 'SampleSelector'],
190
+ frontend_import: "import { toInstrumentRunDataFrame, toInstrumentRunScheduleEvents, toInstrumentRunSteps, toTemplateDataFrame } from '@morscherlab/mint-sdk/templates'",
191
+ frontend_adapters: ['toTemplateDataFrame', 'toInstrumentRunDataFrame', 'toInstrumentRunRows', 'toInstrumentRunColumns', 'toInstrumentRunSteps', 'toInstrumentRunScheduleEvents'],
192
+ components: ['DataFrame', 'ExperimentTimeline', 'ScheduleCalendar', 'SampleSelector'],
193
193
  add_command: 'mint add data-template instrument-run --page',
194
194
  },
195
195
  {
@@ -4,8 +4,11 @@ import {
4
4
  getTemplateData,
5
5
  } from './builders'
6
6
  import {
7
+ toAssayMatrixSampleOptions,
7
8
  toDoseConditions,
8
9
  toDoseLayoutState,
10
+ toInstrumentRunScheduleEvents,
11
+ toInstrumentRunSteps,
9
12
  toPlateMapEditorState,
10
13
  toProtocolSteps,
11
14
  toQpcrWellPlateWells,
@@ -26,8 +29,10 @@ import {
26
29
  } from './packs'
27
30
  import type {
28
31
  BioTemplateEnvelope,
32
+ AssayMatrixTemplate,
29
33
  DataFrameTemplate,
30
34
  DoseResponseTemplate,
35
+ InstrumentRunTemplate,
31
36
  PlateMapTemplate,
32
37
  QpcrPlateTemplate,
33
38
  ReagentListTemplate,
@@ -140,6 +145,7 @@ const templateComponentBindings = {
140
145
  ],
141
146
  'assay-matrix': [
142
147
  binding('assay-matrix', 'DataFrame', ['toTemplateDataFrame'], ['data', 'columns', 'rowKey'], 'Render sample-by-feature measurements.'),
148
+ binding('assay-matrix', 'SampleSelector', ['toAssayMatrixSampleOptions'], ['samples', 'modelValue'], 'Select samples from assay-matrix records.'),
143
149
  ],
144
150
  'reagent-list': [
145
151
  binding('reagent-list', 'ReagentList', ['toReagentListItems'], ['modelValue'], 'Render reagents with lot, storage, and stock metadata.'),
@@ -150,6 +156,20 @@ const templateComponentBindings = {
150
156
  ],
151
157
  'instrument-run': [
152
158
  binding('instrument-run', 'DataFrame', ['toTemplateDataFrame'], ['data', 'columns', 'rowKey'], 'Render acquisition queues, methods, QC, and run status.'),
159
+ binding(
160
+ 'instrument-run',
161
+ 'ScheduleCalendar',
162
+ ['toInstrumentRunScheduleEvents'],
163
+ ['modelValue', 'events', 'view', 'readonly', 'showNavigation', 'showViewToggle'],
164
+ 'Render acquisition queues as a scheduled run calendar.',
165
+ ),
166
+ binding(
167
+ 'instrument-run',
168
+ 'ExperimentTimeline',
169
+ ['toInstrumentRunSteps'],
170
+ ['modelValue', 'orientation', 'showDuration', 'editable'],
171
+ 'Render acquisition queues as an ordered run timeline.',
172
+ ),
153
173
  ],
154
174
  'qpcr-plate': [
155
175
  binding('qpcr-plate', 'WellPlate', ['toQpcrWellPlateWells'], ['format', 'wells', 'sampleColors', 'legendItems'], 'Render qPCR reactions on a well plate.'),
@@ -329,10 +349,19 @@ function propsForTemplate(template: BioTemplateEnvelope<unknown>): BioTemplateCo
329
349
  ]
330
350
  case 'sample-prep':
331
351
  case 'calibration-curve':
332
- case 'assay-matrix':
333
352
  case 'flow-cytometry-panel':
334
- case 'instrument-run':
335
353
  return [dataFrameProps(template as DataFrameTemplate)]
354
+ case 'assay-matrix':
355
+ return [
356
+ dataFrameProps(template as DataFrameTemplate),
357
+ assayMatrixSampleSelectorProps(template as AssayMatrixTemplate),
358
+ ]
359
+ case 'instrument-run':
360
+ return [
361
+ dataFrameProps(template as DataFrameTemplate),
362
+ scheduleCalendarProps(template as InstrumentRunTemplate),
363
+ timelineProps(template as InstrumentRunTemplate, toInstrumentRunSteps(template as InstrumentRunTemplate)),
364
+ ]
336
365
  case 'dose-response':
337
366
  return doseResponseProps(template as DoseResponseTemplate)
338
367
  case 'time-course':
@@ -446,8 +475,16 @@ function sampleSelectorProps(template: SampleSheetTemplate): BioTemplateComponen
446
475
  })
447
476
  }
448
477
 
478
+ function assayMatrixSampleSelectorProps(template: AssayMatrixTemplate): BioTemplateComponentPropsBinding {
479
+ const options = toAssayMatrixSampleOptions(template)
480
+ return propsBinding('assay-matrix', 'SampleSelector', {
481
+ samples: options.map(option => option.label),
482
+ modelValue: [],
483
+ })
484
+ }
485
+
449
486
  function timelineProps(
450
- template: TimeCourseTemplate | ProtocolStepsTemplate,
487
+ template: TimeCourseTemplate | ProtocolStepsTemplate | InstrumentRunTemplate,
451
488
  steps: unknown[],
452
489
  editable = false
453
490
  ): BioTemplateComponentPropsBinding {
@@ -458,6 +495,18 @@ function timelineProps(
458
495
  })
459
496
  }
460
497
 
498
+ function scheduleCalendarProps(template: InstrumentRunTemplate): BioTemplateComponentPropsBinding {
499
+ const events = toInstrumentRunScheduleEvents(template)
500
+ return propsBinding('instrument-run', 'ScheduleCalendar', {
501
+ modelValue: events[0]?.start,
502
+ events,
503
+ view: 'day',
504
+ readonly: true,
505
+ showNavigation: false,
506
+ showViewToggle: false,
507
+ })
508
+ }
509
+
461
510
  function reagentListProps(template: ReagentListTemplate): BioTemplateComponentPropsBinding {
462
511
  return propsBinding('reagent-list', 'ReagentList', {
463
512
  modelValue: toReagentListItems(template),
@@ -106,6 +106,7 @@ export {
106
106
  toAssayMatrixColumns,
107
107
  toAssayMatrixDataFrame,
108
108
  toAssayMatrixRows,
109
+ toAssayMatrixSampleOptions,
109
110
  toCalibrationCurveColumns,
110
111
  toCalibrationCurveDataFrame,
111
112
  toCalibrationCurveRows,
@@ -117,6 +118,8 @@ export {
117
118
  toInstrumentRunColumns,
118
119
  toInstrumentRunDataFrame,
119
120
  toInstrumentRunRows,
121
+ toInstrumentRunScheduleEvents,
122
+ toInstrumentRunSteps,
120
123
  toPlateMapEditorState,
121
124
  toProtocolColumns,
122
125
  toProtocolDataFrame,
@@ -152,6 +155,7 @@ export type {
152
155
  AssayMatrixColumnsAdapterResult,
153
156
  AssayMatrixDataFrameAdapterResult,
154
157
  AssayMatrixRowsAdapterResult,
158
+ AssayMatrixSampleOptionsAdapterResult,
155
159
  AssayMatrixTemplate,
156
160
  AssayMatrixTemplateData,
157
161
  AssayMeasurement,
@@ -211,6 +215,8 @@ export type {
211
215
  InstrumentRunItemInput,
212
216
  InstrumentRunItemKind,
213
217
  InstrumentRunRowsAdapterResult,
218
+ InstrumentRunScheduleEventsAdapterResult,
219
+ InstrumentRunStepsAdapterResult,
214
220
  InstrumentRunStatus,
215
221
  InstrumentRunTemplate,
216
222
  InstrumentRunTemplateData,
@@ -89,7 +89,16 @@ const bioTemplatePackDefinitions = [
89
89
  'calibration-curve',
90
90
  'assay-matrix',
91
91
  ],
92
- aliases: ['omics', 'metabolomics', 'proteomics', 'assay matrix', 'lcms', 'standard curve'],
92
+ aliases: [
93
+ 'omics',
94
+ 'metabolomics',
95
+ 'metabolism',
96
+ 'metabolite profiling',
97
+ 'proteomics',
98
+ 'assay matrix',
99
+ 'lcms',
100
+ 'standard curve',
101
+ ],
93
102
  },
94
103
  {
95
104
  name: 'longitudinal-study',
@@ -57,12 +57,22 @@ export const bioTemplatePresets = [
57
57
  category: 'bio-template-preset',
58
58
  purpose: 'Sample metadata, instrument queue, and assay matrix payloads for omics acquisition batches.',
59
59
  templates: ['sample-sheet', 'instrument-run', 'assay-matrix'],
60
- aliases: ['lcms', 'lc-ms', 'mass spec batch', 'omics batch', 'run queue', 'sequence table'],
60
+ aliases: [
61
+ 'lcms',
62
+ 'lc-ms',
63
+ 'mass spec batch',
64
+ 'omics batch',
65
+ 'metabolomics',
66
+ 'metabolism',
67
+ 'metabolite profiling',
68
+ 'run queue',
69
+ 'sequence table',
70
+ ],
61
71
  python_import: 'from mint_sdk.templates import create_lcms_batch_collection, save_template_collection',
62
72
  python_example: 'create_lcms_batch_collection(samples=["S001", "S002"], features=["Glucose", "Lactate"], instrument="LC-MS")',
63
- frontend_import: "import { createLcmsBatchCollection, toInstrumentRunDataFrame, toTemplateDataFrame } from '@morscherlab/mint-sdk/templates'",
64
- frontend_adapters: ['toTemplateDataFrame', 'toInstrumentRunDataFrame', 'toAssayMatrixDataFrame'],
65
- components: ['DataFrame', 'ScheduleCalendar', 'ChartContainer', 'SampleSelector'],
73
+ frontend_import: "import { createLcmsBatchCollection, toAssayMatrixSampleOptions, toInstrumentRunDataFrame, toInstrumentRunScheduleEvents, toInstrumentRunSteps, toTemplateDataFrame } from '@morscherlab/mint-sdk/templates'",
74
+ frontend_adapters: ['toTemplateDataFrame', 'toInstrumentRunDataFrame', 'toInstrumentRunSteps', 'toInstrumentRunScheduleEvents', 'toAssayMatrixDataFrame', 'toAssayMatrixSampleOptions'],
75
+ components: ['DataFrame', 'ExperimentTimeline', 'ScheduleCalendar', 'ChartContainer', 'SampleSelector'],
66
76
  },
67
77
  {
68
78
  name: 'elisa-assay',
@@ -5,6 +5,7 @@ import type {
5
5
  PlateMapEditorState,
6
6
  ProtocolStep,
7
7
  Reagent,
8
+ ScheduleEvent,
8
9
  SampleType,
9
10
  SelectOption,
10
11
  Well,
@@ -651,6 +652,7 @@ export type ProtocolDataFrameAdapterResult = DataFrameAdapterResult
651
652
  export type AssayMatrixRowsAdapterResult = Array<Record<string, unknown>>
652
653
  export type AssayMatrixColumnsAdapterResult = DataFrameColumn<Record<string, unknown>>[]
653
654
  export type AssayMatrixDataFrameAdapterResult = DataFrameAdapterResult
655
+ export type AssayMatrixSampleOptionsAdapterResult = SelectOption<string>[]
654
656
  export type ReagentListItemsAdapterResult = Reagent[]
655
657
  export type ReagentRowsAdapterResult = Array<Record<string, unknown>>
656
658
  export type ReagentColumnsAdapterResult = DataFrameColumn<Record<string, unknown>>[]
@@ -661,6 +663,8 @@ export type FlowPanelDataFrameAdapterResult = DataFrameAdapterResult
661
663
  export type InstrumentRunRowsAdapterResult = Array<Record<string, unknown>>
662
664
  export type InstrumentRunColumnsAdapterResult = DataFrameColumn<Record<string, unknown>>[]
663
665
  export type InstrumentRunDataFrameAdapterResult = DataFrameAdapterResult
666
+ export type InstrumentRunStepsAdapterResult = ProtocolStep[]
667
+ export type InstrumentRunScheduleEventsAdapterResult = ScheduleEvent[]
664
668
  export type QpcrRowsAdapterResult = Array<Record<string, unknown>>
665
669
  export type QpcrColumnsAdapterResult = DataFrameColumn<Record<string, unknown>>[]
666
670
  export type QpcrDataFrameAdapterResult = DataFrameAdapterResult