@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,6 +1,6 @@
1
- import { Bn as useConcentrationUnits, Bt as extractTemplateCollection, Fn as useControlWorkspace, In as getFieldRegistryEntry, Ln as getTypeDefault, Nt as createTemplateCollection, _ as toBioTemplateComponentPropsByComponent$1, _t as createBioTemplatePresetCollectionFromControls, an as getBioTemplatePackInfo, b as toBioTemplateComponentUsage, d as getBioTemplateComponentProps$1, g as toBioTemplateComponentProps, gt as createBioTemplatePresetCollection, h as toBioTemplateComponentImports, ht as createBioTemplatePackCollection, i as createBioTemplateControlToolkit, m as toBioTemplateComponentBindingsById, p as toBioTemplateComponentBindings, tn as getBioTemplatePresetInfo, u as getBioTemplateComponentBindings, v as toBioTemplateComponentPropsById, y as toBioTemplateComponentSnippets, zt as ensureTemplateFromCollection } from "./templates-BSlxwV2c.js";
2
- import { r as useSettingsStore, t as useAuthStore } from "./auth-CBG3bWEc.js";
3
- import { computed, effectScope, getCurrentScope, onMounted, onScopeDispose, onUnmounted, provide, reactive, readonly, ref, shallowRef, toRaw, toValue, watch } from "vue";
1
+ import { Bt as ensureTemplateFromCollection, In as useControlWorkspace, Ln as getFieldRegistryEntry, Pt as createTemplateCollection, Rn as getTypeDefault, Vn as useConcentrationUnits, Vt as extractTemplateCollection, _ as toBioTemplateComponentPropsByComponent$1, _t as createBioTemplatePresetCollectionFromControls, b as toBioTemplateComponentUsage, d as getBioTemplateComponentProps$1, g as toBioTemplateComponentProps, gt as createBioTemplatePresetCollection, h as toBioTemplateComponentImports, ht as createBioTemplatePackCollection, i as createBioTemplateControlToolkit, m as toBioTemplateComponentBindingsById, nn as getBioTemplatePresetInfo, on as getBioTemplatePackInfo, p as toBioTemplateComponentBindings, u as getBioTemplateComponentBindings, v as toBioTemplateComponentPropsById, y as toBioTemplateComponentSnippets } from "./templates-BorLR_7p.js";
2
+ import { g as useSettingsStore, t as useAuthStore } from "./auth-B7g4J4ZF.js";
3
+ import { computed, effectScope, getCurrentScope, inject, onMounted, onScopeDispose, onUnmounted, provide, reactive, readonly, ref, shallowRef, toRaw, toValue, watch } from "vue";
4
4
  import axios from "axios";
5
5
  //#region src/composables/useSortedItems.ts
6
6
  /** Shared sorting for SDK tables and lists with stable empty-value handling. */
@@ -673,6 +673,223 @@ function useFormBuilder(schema, initialData, enhancements) {
673
673
  };
674
674
  }
675
675
  //#endregion
676
+ //#region src/composables/usePlatformContext.ts
677
+ var DEFAULT_CONTEXT = {
678
+ isIntegrated: false,
679
+ theme: "system"
680
+ };
681
+ var platformContext = ref({ ...DEFAULT_CONTEXT });
682
+ var inferredOrigins = /* @__PURE__ */ new Set();
683
+ var allowedOrigins = /* @__PURE__ */ new Set();
684
+ var allowAnyOrigin = false;
685
+ var initialized = false;
686
+ var listenerCount = 0;
687
+ var nextConsumerId = 0;
688
+ var currentHandler = null;
689
+ var consumerOrigins = /* @__PURE__ */ new Map();
690
+ var allowAnyOriginConsumers = /* @__PURE__ */ new Set();
691
+ /**
692
+ * Derive origin from URL (protocol + host)
693
+ */
694
+ function getOriginFromUrl(url) {
695
+ try {
696
+ return new URL(url).origin;
697
+ } catch {
698
+ return null;
699
+ }
700
+ }
701
+ function normalizeAllowedOrigins(origins) {
702
+ const normalized = /* @__PURE__ */ new Set();
703
+ if (!origins) return normalized;
704
+ for (const origin of origins) normalized.add(getOriginFromUrl(origin) || origin);
705
+ return normalized;
706
+ }
707
+ function recomputeOriginPolicy() {
708
+ allowedOrigins = new Set(inferredOrigins);
709
+ for (const origins of consumerOrigins.values()) for (const origin of origins) allowedOrigins.add(origin);
710
+ allowAnyOrigin = allowAnyOriginConsumers.size > 0;
711
+ }
712
+ function resetPlatformContextState() {
713
+ inferredOrigins = /* @__PURE__ */ new Set();
714
+ allowedOrigins = /* @__PURE__ */ new Set();
715
+ allowAnyOrigin = false;
716
+ initialized = false;
717
+ listenerCount = 0;
718
+ currentHandler = null;
719
+ consumerOrigins.clear();
720
+ allowAnyOriginConsumers.clear();
721
+ platformContext.value = { ...DEFAULT_CONTEXT };
722
+ }
723
+ /**
724
+ * Check if an origin is allowed for postMessage communication
725
+ */
726
+ function isOriginAllowed(origin) {
727
+ if (allowAnyOrigin) {
728
+ console.warn("[MINT SDK] postMessage origin validation disabled - only use in development");
729
+ return true;
730
+ }
731
+ if (origin === window.location.origin) return true;
732
+ return allowedOrigins.has(origin);
733
+ }
734
+ /**
735
+ * Platform context composable for plugin integration with MINT Platform.
736
+ *
737
+ * Provides secure communication with the parent platform via postMessage.
738
+ *
739
+ * @param options - Configuration options
740
+ * @param options.allowedOrigins - List of allowed origins for postMessage
741
+ * @param options.allowAnyOrigin - Allow any origin (UNSAFE, development only)
742
+ *
743
+ * @example
744
+ * ```typescript
745
+ * // Basic usage - derives origin from platform injection
746
+ * const { isIntegrated, user, theme } = usePlatformContext()
747
+ *
748
+ * // With explicit allowed origins
749
+ * const { isIntegrated } = usePlatformContext({
750
+ * allowedOrigins: ['https://mint.example.com']
751
+ * })
752
+ *
753
+ * // Development mode (UNSAFE)
754
+ * const { isIntegrated } = usePlatformContext({
755
+ * allowAnyOrigin: import.meta.env.DEV
756
+ * })
757
+ * ```
758
+ */
759
+ /** Connects a plugin to the MINT platform via postMessage, exposing user, theme, and experiment context. */
760
+ function usePlatformContext(options = {}) {
761
+ const consumerId = ++nextConsumerId;
762
+ const instanceOrigins = normalizeAllowedOrigins(options.allowedOrigins);
763
+ const instanceAllowAnyOrigin = options.allowAnyOrigin === true;
764
+ function detectPlatform() {
765
+ const detectedOrigins = /* @__PURE__ */ new Set();
766
+ const platformData = window.__MINT_PLATFORM__;
767
+ if (platformData) {
768
+ platformContext.value = {
769
+ ...platformData,
770
+ isIntegrated: true
771
+ };
772
+ if (platformData.platformOrigin) detectedOrigins.add(platformData.platformOrigin);
773
+ else if (platformData.platformApiUrl) {
774
+ const origin = getOriginFromUrl(platformData.platformApiUrl);
775
+ if (origin) detectedOrigins.add(origin);
776
+ }
777
+ } else {
778
+ const urlParams = new URLSearchParams(window.location.search);
779
+ const hasPluginParam = urlParams.has("mint-plugin");
780
+ const platformOrigin = urlParams.get("mint-origin");
781
+ if (platformOrigin) {
782
+ const origin = getOriginFromUrl(platformOrigin);
783
+ if (origin) detectedOrigins.add(origin);
784
+ }
785
+ platformContext.value = {
786
+ isIntegrated: hasPluginParam,
787
+ theme: (() => {
788
+ try {
789
+ const s = localStorage.getItem("mint-settings");
790
+ if (s) return JSON.parse(s).theme || "system";
791
+ } catch {}
792
+ return "system";
793
+ })(),
794
+ platformOrigin: platformOrigin || void 0
795
+ };
796
+ }
797
+ inferredOrigins = detectedOrigins;
798
+ }
799
+ function handlePlatformMessage(event) {
800
+ if (event.source !== window.parent) return;
801
+ if (!isOriginAllowed(event.origin)) {
802
+ console.warn(`[MINT SDK] Rejected postMessage from untrusted origin: ${event.origin}`);
803
+ return;
804
+ }
805
+ try {
806
+ const platformEvent = event.data;
807
+ if (!platformEvent.type?.startsWith("mint:")) return;
808
+ switch (platformEvent.type) {
809
+ case "mint:theme-changed":
810
+ platformContext.value.theme = platformEvent.payload;
811
+ break;
812
+ case "mint:user-changed":
813
+ platformContext.value.user = platformEvent.payload;
814
+ break;
815
+ }
816
+ } catch {}
817
+ }
818
+ /**
819
+ * Send a message to the parent platform.
820
+ * Uses validated target origin for security.
821
+ */
822
+ function sendToPlatform(event) {
823
+ if (!platformContext.value.isIntegrated || window.parent === window) return;
824
+ let targetOrigin;
825
+ if (platformContext.value.platformOrigin) targetOrigin = platformContext.value.platformOrigin;
826
+ else if (allowedOrigins.size > 0) targetOrigin = allowedOrigins.values().next().value;
827
+ else if (allowAnyOrigin) {
828
+ targetOrigin = "*";
829
+ console.warn("[MINT SDK] Using wildcard origin for postMessage - only use in development");
830
+ } else {
831
+ console.warn("[MINT SDK] Cannot send postMessage: no platform origin configured");
832
+ return;
833
+ }
834
+ window.parent.postMessage(event, targetOrigin);
835
+ }
836
+ /**
837
+ * Request navigation to a path in the platform.
838
+ */
839
+ function navigate(path) {
840
+ sendToPlatform({
841
+ type: "mint:navigate",
842
+ payload: path
843
+ });
844
+ }
845
+ /**
846
+ * Show a notification in the platform.
847
+ */
848
+ function notify(message, type = "info") {
849
+ sendToPlatform({
850
+ type: "mint:notification",
851
+ payload: {
852
+ message,
853
+ type
854
+ }
855
+ });
856
+ }
857
+ onMounted(() => {
858
+ consumerOrigins.set(consumerId, instanceOrigins);
859
+ if (instanceAllowAnyOrigin) allowAnyOriginConsumers.add(consumerId);
860
+ if (!initialized) {
861
+ detectPlatform();
862
+ currentHandler = handlePlatformMessage;
863
+ window.addEventListener("message", handlePlatformMessage);
864
+ initialized = true;
865
+ }
866
+ listenerCount++;
867
+ recomputeOriginPolicy();
868
+ });
869
+ onUnmounted(() => {
870
+ consumerOrigins.delete(consumerId);
871
+ allowAnyOriginConsumers.delete(consumerId);
872
+ listenerCount = Math.max(0, listenerCount - 1);
873
+ if (listenerCount === 0) {
874
+ if (currentHandler) window.removeEventListener("message", currentHandler);
875
+ resetPlatformContextState();
876
+ return;
877
+ }
878
+ recomputeOriginPolicy();
879
+ });
880
+ return {
881
+ context: platformContext,
882
+ isIntegrated: computed(() => platformContext.value.isIntegrated),
883
+ plugin: computed(() => platformContext.value.plugin),
884
+ user: computed(() => platformContext.value.user),
885
+ theme: computed(() => platformContext.value.theme),
886
+ features: computed(() => platformContext.value.features),
887
+ navigate,
888
+ notify,
889
+ sendToPlatform
890
+ };
891
+ }
892
+ //#endregion
676
893
  //#region src/composables/experiment-utils.ts
677
894
  function formatExperimentDate(dateStr) {
678
895
  try {
@@ -1191,270 +1408,53 @@ function useExperimentSelector(options = {}) {
1191
1408
  const groupedByProject = computed(() => {
1192
1409
  const groups = /* @__PURE__ */ new Map();
1193
1410
  for (const exp of experiments.value) {
1194
- const key = exp.project_name ?? exp.project ?? "No project";
1195
- const list = groups.get(key);
1196
- if (list) list.push(exp);
1197
- else groups.set(key, [exp]);
1198
- }
1199
- return [...groups.entries()].sort(([a], [b]) => {
1200
- if (a === "No project") return 1;
1201
- if (b === "No project") return -1;
1202
- return a.localeCompare(b);
1203
- });
1204
- });
1205
- const searchWatch = useDebouncedWatch(() => filters.search, () => {
1206
- page.value = 0;
1207
- fetchExperiments();
1208
- }, { delay: 300 });
1209
- watch(() => [
1210
- filters.status,
1211
- filters.project,
1212
- filters.experimentType,
1213
- filters.datePreset,
1214
- sortKey.value
1215
- ], () => {
1216
- searchWatch.cancel();
1217
- page.value = 0;
1218
- fetchExperiments();
1219
- });
1220
- if (immediate) fetchExperiments();
1221
- return {
1222
- experiments,
1223
- total,
1224
- selectedExperiment,
1225
- filters,
1226
- isLoading,
1227
- error,
1228
- lastLoadedAt,
1229
- page,
1230
- hasMore,
1231
- sortKey,
1232
- experimentTypes,
1233
- projects,
1234
- groupedByProject,
1235
- fetch: fetchExperiments,
1236
- loadMore,
1237
- reset,
1238
- select,
1239
- clear,
1240
- fetchFilterOptions
1241
- };
1242
- }
1243
- //#endregion
1244
- //#region src/composables/usePlatformContext.ts
1245
- var DEFAULT_CONTEXT = {
1246
- isIntegrated: false,
1247
- theme: "system"
1248
- };
1249
- var platformContext = ref({ ...DEFAULT_CONTEXT });
1250
- var inferredOrigins = /* @__PURE__ */ new Set();
1251
- var allowedOrigins = /* @__PURE__ */ new Set();
1252
- var allowAnyOrigin = false;
1253
- var initialized = false;
1254
- var listenerCount = 0;
1255
- var nextConsumerId = 0;
1256
- var currentHandler = null;
1257
- var consumerOrigins = /* @__PURE__ */ new Map();
1258
- var allowAnyOriginConsumers = /* @__PURE__ */ new Set();
1259
- /**
1260
- * Derive origin from URL (protocol + host)
1261
- */
1262
- function getOriginFromUrl(url) {
1263
- try {
1264
- return new URL(url).origin;
1265
- } catch {
1266
- return null;
1267
- }
1268
- }
1269
- function normalizeAllowedOrigins(origins) {
1270
- const normalized = /* @__PURE__ */ new Set();
1271
- if (!origins) return normalized;
1272
- for (const origin of origins) normalized.add(getOriginFromUrl(origin) || origin);
1273
- return normalized;
1274
- }
1275
- function recomputeOriginPolicy() {
1276
- allowedOrigins = new Set(inferredOrigins);
1277
- for (const origins of consumerOrigins.values()) for (const origin of origins) allowedOrigins.add(origin);
1278
- allowAnyOrigin = allowAnyOriginConsumers.size > 0;
1279
- }
1280
- function resetPlatformContextState() {
1281
- inferredOrigins = /* @__PURE__ */ new Set();
1282
- allowedOrigins = /* @__PURE__ */ new Set();
1283
- allowAnyOrigin = false;
1284
- initialized = false;
1285
- listenerCount = 0;
1286
- currentHandler = null;
1287
- consumerOrigins.clear();
1288
- allowAnyOriginConsumers.clear();
1289
- platformContext.value = { ...DEFAULT_CONTEXT };
1290
- }
1291
- /**
1292
- * Check if an origin is allowed for postMessage communication
1293
- */
1294
- function isOriginAllowed(origin) {
1295
- if (allowAnyOrigin) {
1296
- console.warn("[MINT SDK] postMessage origin validation disabled - only use in development");
1297
- return true;
1298
- }
1299
- if (origin === window.location.origin) return true;
1300
- return allowedOrigins.has(origin);
1301
- }
1302
- /**
1303
- * Platform context composable for plugin integration with MINT Platform.
1304
- *
1305
- * Provides secure communication with the parent platform via postMessage.
1306
- *
1307
- * @param options - Configuration options
1308
- * @param options.allowedOrigins - List of allowed origins for postMessage
1309
- * @param options.allowAnyOrigin - Allow any origin (UNSAFE, development only)
1310
- *
1311
- * @example
1312
- * ```typescript
1313
- * // Basic usage - derives origin from platform injection
1314
- * const { isIntegrated, user, theme } = usePlatformContext()
1315
- *
1316
- * // With explicit allowed origins
1317
- * const { isIntegrated } = usePlatformContext({
1318
- * allowedOrigins: ['https://mint.example.com']
1319
- * })
1320
- *
1321
- * // Development mode (UNSAFE)
1322
- * const { isIntegrated } = usePlatformContext({
1323
- * allowAnyOrigin: import.meta.env.DEV
1324
- * })
1325
- * ```
1326
- */
1327
- /** Connects a plugin to the MINT platform via postMessage, exposing user, theme, and experiment context. */
1328
- function usePlatformContext(options = {}) {
1329
- const consumerId = ++nextConsumerId;
1330
- const instanceOrigins = normalizeAllowedOrigins(options.allowedOrigins);
1331
- const instanceAllowAnyOrigin = options.allowAnyOrigin === true;
1332
- function detectPlatform() {
1333
- const detectedOrigins = /* @__PURE__ */ new Set();
1334
- const platformData = window.__MINT_PLATFORM__;
1335
- if (platformData) {
1336
- platformContext.value = {
1337
- ...platformData,
1338
- isIntegrated: true
1339
- };
1340
- if (platformData.platformOrigin) detectedOrigins.add(platformData.platformOrigin);
1341
- else if (platformData.platformApiUrl) {
1342
- const origin = getOriginFromUrl(platformData.platformApiUrl);
1343
- if (origin) detectedOrigins.add(origin);
1344
- }
1345
- } else {
1346
- const urlParams = new URLSearchParams(window.location.search);
1347
- const hasPluginParam = urlParams.has("mint-plugin");
1348
- const platformOrigin = urlParams.get("mint-origin");
1349
- if (platformOrigin) {
1350
- const origin = getOriginFromUrl(platformOrigin);
1351
- if (origin) detectedOrigins.add(origin);
1352
- }
1353
- platformContext.value = {
1354
- isIntegrated: hasPluginParam,
1355
- theme: (() => {
1356
- try {
1357
- const s = localStorage.getItem("mint-settings");
1358
- if (s) return JSON.parse(s).theme || "system";
1359
- } catch {}
1360
- return "system";
1361
- })(),
1362
- platformOrigin: platformOrigin || void 0
1363
- };
1364
- }
1365
- inferredOrigins = detectedOrigins;
1366
- }
1367
- function handlePlatformMessage(event) {
1368
- if (event.source !== window.parent) return;
1369
- if (!isOriginAllowed(event.origin)) {
1370
- console.warn(`[MINT SDK] Rejected postMessage from untrusted origin: ${event.origin}`);
1371
- return;
1372
- }
1373
- try {
1374
- const platformEvent = event.data;
1375
- if (!platformEvent.type?.startsWith("mint:")) return;
1376
- switch (platformEvent.type) {
1377
- case "mint:theme-changed":
1378
- platformContext.value.theme = platformEvent.payload;
1379
- break;
1380
- case "mint:user-changed":
1381
- platformContext.value.user = platformEvent.payload;
1382
- break;
1383
- }
1384
- } catch {}
1385
- }
1386
- /**
1387
- * Send a message to the parent platform.
1388
- * Uses validated target origin for security.
1389
- */
1390
- function sendToPlatform(event) {
1391
- if (!platformContext.value.isIntegrated || window.parent === window) return;
1392
- let targetOrigin;
1393
- if (platformContext.value.platformOrigin) targetOrigin = platformContext.value.platformOrigin;
1394
- else if (allowedOrigins.size > 0) targetOrigin = allowedOrigins.values().next().value;
1395
- else if (allowAnyOrigin) {
1396
- targetOrigin = "*";
1397
- console.warn("[MINT SDK] Using wildcard origin for postMessage - only use in development");
1398
- } else {
1399
- console.warn("[MINT SDK] Cannot send postMessage: no platform origin configured");
1400
- return;
1401
- }
1402
- window.parent.postMessage(event, targetOrigin);
1403
- }
1404
- /**
1405
- * Request navigation to a path in the platform.
1406
- */
1407
- function navigate(path) {
1408
- sendToPlatform({
1409
- type: "mint:navigate",
1410
- payload: path
1411
- });
1412
- }
1413
- /**
1414
- * Show a notification in the platform.
1415
- */
1416
- function notify(message, type = "info") {
1417
- sendToPlatform({
1418
- type: "mint:notification",
1419
- payload: {
1420
- message,
1421
- type
1422
- }
1423
- });
1424
- }
1425
- onMounted(() => {
1426
- consumerOrigins.set(consumerId, instanceOrigins);
1427
- if (instanceAllowAnyOrigin) allowAnyOriginConsumers.add(consumerId);
1428
- if (!initialized) {
1429
- detectPlatform();
1430
- currentHandler = handlePlatformMessage;
1431
- window.addEventListener("message", handlePlatformMessage);
1432
- initialized = true;
1433
- }
1434
- listenerCount++;
1435
- recomputeOriginPolicy();
1436
- });
1437
- onUnmounted(() => {
1438
- consumerOrigins.delete(consumerId);
1439
- allowAnyOriginConsumers.delete(consumerId);
1440
- listenerCount = Math.max(0, listenerCount - 1);
1441
- if (listenerCount === 0) {
1442
- if (currentHandler) window.removeEventListener("message", currentHandler);
1443
- resetPlatformContextState();
1444
- return;
1411
+ const key = exp.project_name ?? exp.project ?? "No project";
1412
+ const list = groups.get(key);
1413
+ if (list) list.push(exp);
1414
+ else groups.set(key, [exp]);
1445
1415
  }
1446
- recomputeOriginPolicy();
1416
+ return [...groups.entries()].sort(([a], [b]) => {
1417
+ if (a === "No project") return 1;
1418
+ if (b === "No project") return -1;
1419
+ return a.localeCompare(b);
1420
+ });
1421
+ });
1422
+ const searchWatch = useDebouncedWatch(() => filters.search, () => {
1423
+ page.value = 0;
1424
+ fetchExperiments();
1425
+ }, { delay: 300 });
1426
+ watch(() => [
1427
+ filters.status,
1428
+ filters.project,
1429
+ filters.experimentType,
1430
+ filters.datePreset,
1431
+ sortKey.value
1432
+ ], () => {
1433
+ searchWatch.cancel();
1434
+ page.value = 0;
1435
+ fetchExperiments();
1447
1436
  });
1437
+ if (immediate) fetchExperiments();
1448
1438
  return {
1449
- context: platformContext,
1450
- isIntegrated: computed(() => platformContext.value.isIntegrated),
1451
- plugin: computed(() => platformContext.value.plugin),
1452
- user: computed(() => platformContext.value.user),
1453
- theme: computed(() => platformContext.value.theme),
1454
- features: computed(() => platformContext.value.features),
1455
- navigate,
1456
- notify,
1457
- sendToPlatform
1439
+ experiments,
1440
+ total,
1441
+ selectedExperiment,
1442
+ filters,
1443
+ isLoading,
1444
+ error,
1445
+ lastLoadedAt,
1446
+ page,
1447
+ hasMore,
1448
+ sortKey,
1449
+ experimentTypes,
1450
+ projects,
1451
+ groupedByProject,
1452
+ fetch: fetchExperiments,
1453
+ loadMore,
1454
+ reset,
1455
+ select,
1456
+ clear,
1457
+ fetchFilterOptions
1458
1458
  };
1459
1459
  }
1460
1460
  //#endregion
@@ -2098,6 +2098,108 @@ function useWellPlateEditor(initialState, options = {}) {
2098
2098
  };
2099
2099
  }
2100
2100
  //#endregion
2101
+ //#region src/composables/experimentDesignData.ts
2102
+ function isRecord(value) {
2103
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2104
+ }
2105
+ function isPlatformDesignDataResponse(value) {
2106
+ return "data" in value && ("experiment_id" in value || "plugin_id" in value || "schema_version" in value || "updated_at" in value);
2107
+ }
2108
+ /** Return the plugin-defined design_data payload from common platform and plugin response shapes. */
2109
+ function unwrapExperimentDesignData(rawData) {
2110
+ if (!rawData) return null;
2111
+ if (isRecord(rawData.data) && isPlatformDesignDataResponse(rawData)) return rawData.data;
2112
+ if (isRecord(rawData.design_data)) return rawData.design_data;
2113
+ if (isRecord(rawData.designData)) return rawData.designData;
2114
+ return rawData;
2115
+ }
2116
+ function shouldIncludeSample(record, options) {
2117
+ if (options.includeControls) return true;
2118
+ const rawType = record.sample_type ?? record.sampleType ?? record.type ?? record.well_type ?? record.wellType;
2119
+ const type = typeof rawType === "string" ? rawType.toLowerCase() : "";
2120
+ return !["blank", "qc"].includes(type);
2121
+ }
2122
+ function readSampleValue(record) {
2123
+ const candidates = [
2124
+ record.sample_name,
2125
+ record.sampleName,
2126
+ record.sample_id,
2127
+ record.sampleId,
2128
+ record.name,
2129
+ record.id,
2130
+ record.label
2131
+ ];
2132
+ for (const candidate of candidates) {
2133
+ if (typeof candidate === "string" && candidate.trim()) return candidate;
2134
+ if (typeof candidate === "number" && Number.isFinite(candidate)) return String(candidate);
2135
+ }
2136
+ return null;
2137
+ }
2138
+ function readSampleLabel(record, fallback) {
2139
+ const label = record.name ?? record.label ?? record.sample_name ?? record.sampleName;
2140
+ return typeof label === "string" && label.trim() ? label : fallback;
2141
+ }
2142
+ function readSampleDescription(record) {
2143
+ const parts = [
2144
+ record.group,
2145
+ record.batch,
2146
+ record.batch_id,
2147
+ record.batchId,
2148
+ record.plate_id,
2149
+ record.plateId,
2150
+ record.well_id,
2151
+ record.wellId
2152
+ ].filter((value) => typeof value === "string" && value.trim().length > 0 || typeof value === "number");
2153
+ return parts.length > 0 ? parts.map(String).join(" / ") : void 0;
2154
+ }
2155
+ function addSampleRecord(options, seen, value, extractOptions) {
2156
+ if (typeof value === "string") {
2157
+ const trimmed = value.trim();
2158
+ if (trimmed && !seen.has(trimmed)) {
2159
+ seen.add(trimmed);
2160
+ options.push({
2161
+ value: trimmed,
2162
+ label: trimmed
2163
+ });
2164
+ }
2165
+ return;
2166
+ }
2167
+ if (!isRecord(value) || !shouldIncludeSample(value, extractOptions)) return;
2168
+ const sampleValue = readSampleValue(value);
2169
+ if (!sampleValue || seen.has(sampleValue)) return;
2170
+ seen.add(sampleValue);
2171
+ options.push({
2172
+ value: sampleValue,
2173
+ label: readSampleLabel(value, sampleValue),
2174
+ description: readSampleDescription(value)
2175
+ });
2176
+ }
2177
+ function addSampleArray(options, seen, value, extractOptions) {
2178
+ if (!Array.isArray(value)) return;
2179
+ for (const sample of value) addSampleRecord(options, seen, sample, extractOptions);
2180
+ }
2181
+ /** Extract selectable sample options from raw or wrapped experiment design_data. */
2182
+ function extractSampleOptionsFromDesignData(rawData, options = {}) {
2183
+ const designData = unwrapExperimentDesignData(rawData);
2184
+ if (!designData) return [];
2185
+ const sampleOptions = [];
2186
+ const seen = /* @__PURE__ */ new Set();
2187
+ addSampleArray(sampleOptions, seen, designData.samples, options);
2188
+ if (isRecord(designData.templates)) for (const template of Object.values(designData.templates)) {
2189
+ if (!isRecord(template)) continue;
2190
+ addSampleArray(sampleOptions, seen, (isRecord(template.data) ? template.data : template).samples, options);
2191
+ }
2192
+ if (Array.isArray(designData.plates)) for (const plate of designData.plates) {
2193
+ if (!isRecord(plate)) continue;
2194
+ addSampleArray(sampleOptions, seen, plate.samples, options);
2195
+ }
2196
+ return sampleOptions;
2197
+ }
2198
+ /** Extract SampleSelector-compatible string values from raw or wrapped experiment design_data. */
2199
+ function extractSampleNamesFromDesignData(rawData, options = {}) {
2200
+ return extractSampleOptionsFromDesignData(rawData, options).map((sample) => sample.value);
2201
+ }
2202
+ //#endregion
2101
2203
  //#region src/composables/useAutoGroup.ts
2102
2204
  var DEFAULT_COLORS = [
2103
2205
  "#3B82F6",
@@ -2323,7 +2425,9 @@ function computeGroups(allSamples, columns, enabledFields, outlierActions, delim
2323
2425
  * Returns null if no samples with conditions are found.
2324
2426
  */
2325
2427
  function extractSamplesFromDesignData(rawData) {
2326
- const samples = rawData.samples;
2428
+ const designData = unwrapExperimentDesignData(rawData);
2429
+ if (!designData) return null;
2430
+ const samples = designData.samples;
2327
2431
  if (!Array.isArray(samples) || samples.length === 0) return null;
2328
2432
  const allConditionKeys = [];
2329
2433
  const keySet = /* @__PURE__ */ new Set();
@@ -2772,6 +2876,169 @@ function normalizeIds(ids) {
2772
2876
  return [...new Set(ids.filter(Boolean))];
2773
2877
  }
2774
2878
  //#endregion
2879
+ //#region src/composables/platformContextHelpers.ts
2880
+ function getInjectedPlatformContext() {
2881
+ if (typeof window === "undefined") return void 0;
2882
+ return window.__MINT_PLATFORM__;
2883
+ }
2884
+ function getInjectedExperimentContext() {
2885
+ return getInjectedPlatformContext();
2886
+ }
2887
+ function currentExperimentFromContext(context = getInjectedExperimentContext()) {
2888
+ const candidate = context?.currentExperiment ?? context?.experiment;
2889
+ return candidate && typeof candidate === "object" ? candidate : void 0;
2890
+ }
2891
+ function currentExperimentIdFromContext(context = getInjectedExperimentContext()) {
2892
+ return parseExperimentId(context?.currentExperimentId ?? context?.experimentId ?? context?.currentExperiment ?? context?.experiment);
2893
+ }
2894
+ function currentExperimentIdFromUrl() {
2895
+ if (typeof window === "undefined") return void 0;
2896
+ const params = new URLSearchParams(window.location.search);
2897
+ return parseExperimentId(params.get("experimentId") ?? params.get("experiment_id")) ?? experimentIdFromPath(window.location.pathname) ?? experimentIdFromPath(window.location.hash.replace(/^#\/?/, "/"));
2898
+ }
2899
+ function resolveCurrentExperimentId(context = getInjectedExperimentContext()) {
2900
+ return currentExperimentIdFromContext(context) ?? currentExperimentIdFromUrl();
2901
+ }
2902
+ function parseExperimentId(value) {
2903
+ if (typeof value === "number" && Number.isFinite(value)) return value;
2904
+ if (typeof value === "string" && value.trim()) {
2905
+ const parsed = Number(value);
2906
+ return Number.isFinite(parsed) ? parsed : void 0;
2907
+ }
2908
+ if (value && typeof value === "object" && "id" in value) return parseExperimentId(value.id);
2909
+ }
2910
+ function experimentIdFromPath(path) {
2911
+ const segments = path.split("/").filter(Boolean);
2912
+ for (let index = 0; index < segments.length - 1; index += 1) {
2913
+ const segment = segments[index]?.toLowerCase();
2914
+ if (segment === "experiment" || segment === "experiments") return parseExperimentId(decodeURIComponent(segments[index + 1] ?? ""));
2915
+ }
2916
+ }
2917
+ //#endregion
2918
+ //#region src/composables/useExperimentData.ts
2919
+ /** Fetches and normalises experiment output data (tree, table, summary) from the platform API. */
2920
+ function useExperimentData(options = {}) {
2921
+ const data = shallowRef(null);
2922
+ const request = useRequestSyncState("Failed to fetch experiment data");
2923
+ const isLoading = request.loading;
2924
+ const error = request.error;
2925
+ const lastLoadedAt = request.lastLoadedAt;
2926
+ let lastExperimentId = null;
2927
+ let api = null;
2928
+ function getApi() {
2929
+ api ??= useApi({ baseUrl: options.apiBaseUrl });
2930
+ return api;
2931
+ }
2932
+ const designData = computed(() => unwrapExperimentDesignData(data.value));
2933
+ const sampleNames = computed(() => extractSampleNamesFromDesignData(designData.value));
2934
+ const sampleOptions = computed(() => extractSampleOptionsFromDesignData(designData.value));
2935
+ const treeData = computed(() => {
2936
+ if (!designData.value) return [];
2937
+ const tree = designData.value.tree_data ?? designData.value.treeData;
2938
+ return Array.isArray(tree) ? tree : [];
2939
+ });
2940
+ const tableData = computed(() => {
2941
+ if (!designData.value) return [];
2942
+ const table = designData.value.table_data ?? designData.value.tableData;
2943
+ return Array.isArray(table) ? table : [];
2944
+ });
2945
+ const summaryData = computed(() => {
2946
+ if (!designData.value) return null;
2947
+ const summary = designData.value.summary_data ?? designData.value.summaryData;
2948
+ if (summary && typeof summary === "object" && "metadata" in summary) return summary;
2949
+ return null;
2950
+ });
2951
+ async function fetchData(experimentId) {
2952
+ lastExperimentId = experimentId;
2953
+ try {
2954
+ data.value = await request.run(() => getApi().get(`/experiments/${experimentId}/data`), {
2955
+ success: "load",
2956
+ errorMessage: "Failed to fetch experiment data"
2957
+ });
2958
+ return data.value;
2959
+ } catch {
2960
+ data.value = null;
2961
+ return null;
2962
+ }
2963
+ }
2964
+ async function refresh() {
2965
+ if (lastExperimentId !== null) await fetchData(lastExperimentId);
2966
+ }
2967
+ function clear() {
2968
+ data.value = null;
2969
+ lastExperimentId = null;
2970
+ request.clearError();
2971
+ }
2972
+ return {
2973
+ data,
2974
+ designData,
2975
+ sampleNames,
2976
+ sampleOptions,
2977
+ treeData,
2978
+ tableData,
2979
+ summaryData,
2980
+ isLoading,
2981
+ error,
2982
+ lastLoadedAt,
2983
+ fetch: fetchData,
2984
+ refresh,
2985
+ clear
2986
+ };
2987
+ }
2988
+ //#endregion
2989
+ //#region src/composables/useExperimentSamples.ts
2990
+ /** Loads experiment design_data and derives samples for SampleSelector-style UIs. */
2991
+ function useExperimentSamples(options = {}) {
2992
+ const appExperiment = options.syncAppExperiment === false ? void 0 : inject(APP_EXPERIMENT_KEY, void 0);
2993
+ const experimentData = useExperimentData({ apiBaseUrl: options.apiBaseUrl });
2994
+ const experimentId = computed(() => {
2995
+ return parseExperimentId(toValue(options.experimentId)) ?? parseExperimentId(appExperiment?.experimentId.value);
2996
+ });
2997
+ const explicitDesignData = computed(() => unwrapExperimentDesignData(toValue(options.designData) ?? null));
2998
+ const designData = computed(() => explicitDesignData.value ?? experimentData.designData.value);
2999
+ const extractOptions = computed(() => ({ includeControls: options.includeControls }));
3000
+ const samples = computed(() => extractSampleNamesFromDesignData(designData.value, extractOptions.value));
3001
+ const sampleOptions = computed(() => extractSampleOptionsFromDesignData(designData.value, extractOptions.value));
3002
+ const enabled = computed(() => toValue(options.enabled) ?? true);
3003
+ async function fetchSamples(id = experimentId.value) {
3004
+ const parsedId = parseExperimentId(id);
3005
+ if (parsedId === void 0) {
3006
+ experimentData.clear();
3007
+ return null;
3008
+ }
3009
+ return experimentData.fetch(parsedId);
3010
+ }
3011
+ watch(() => ({
3012
+ designData: explicitDesignData.value,
3013
+ enabled: enabled.value,
3014
+ experimentId: experimentId.value
3015
+ }), ({ designData: currentDesignData, enabled: isEnabled, experimentId: currentExperimentId }) => {
3016
+ if (options.immediate === false || !isEnabled) return;
3017
+ if (currentDesignData) {
3018
+ experimentData.clear();
3019
+ return;
3020
+ }
3021
+ if (currentExperimentId === void 0) {
3022
+ experimentData.clear();
3023
+ return;
3024
+ }
3025
+ experimentData.fetch(currentExperimentId);
3026
+ }, { immediate: options.immediate !== false });
3027
+ return {
3028
+ experimentId,
3029
+ data: experimentData.data,
3030
+ designData,
3031
+ samples,
3032
+ sampleOptions,
3033
+ isLoading: experimentData.isLoading,
3034
+ error: experimentData.error,
3035
+ lastLoadedAt: experimentData.lastLoadedAt,
3036
+ fetch: fetchSamples,
3037
+ refresh: experimentData.refresh,
3038
+ clear: experimentData.clear
3039
+ };
3040
+ }
3041
+ //#endregion
2775
3042
  //#region src/composables/useScheduleDrag.ts
2776
3043
  /** Handles pointer-driven create, move, and resize drag interactions for the ScheduleCalendar grid. */
2777
3044
  function useScheduleDrag(options) {
@@ -2952,45 +3219,6 @@ function clampRange(start, end, min, max) {
2952
3219
  };
2953
3220
  }
2954
3221
  //#endregion
2955
- //#region src/composables/platformContextHelpers.ts
2956
- function getInjectedPlatformContext() {
2957
- if (typeof window === "undefined") return void 0;
2958
- return window.__MINT_PLATFORM__;
2959
- }
2960
- function getInjectedExperimentContext() {
2961
- return getInjectedPlatformContext();
2962
- }
2963
- function currentExperimentFromContext(context = getInjectedExperimentContext()) {
2964
- const candidate = context?.currentExperiment ?? context?.experiment;
2965
- return candidate && typeof candidate === "object" ? candidate : void 0;
2966
- }
2967
- function currentExperimentIdFromContext(context = getInjectedExperimentContext()) {
2968
- return parseExperimentId(context?.currentExperimentId ?? context?.experimentId ?? context?.currentExperiment ?? context?.experiment);
2969
- }
2970
- function currentExperimentIdFromUrl() {
2971
- if (typeof window === "undefined") return void 0;
2972
- const params = new URLSearchParams(window.location.search);
2973
- return parseExperimentId(params.get("experimentId") ?? params.get("experiment_id")) ?? experimentIdFromPath(window.location.pathname) ?? experimentIdFromPath(window.location.hash.replace(/^#\/?/, "/"));
2974
- }
2975
- function resolveCurrentExperimentId(context = getInjectedExperimentContext()) {
2976
- return currentExperimentIdFromContext(context) ?? currentExperimentIdFromUrl();
2977
- }
2978
- function parseExperimentId(value) {
2979
- if (typeof value === "number" && Number.isFinite(value)) return value;
2980
- if (typeof value === "string" && value.trim()) {
2981
- const parsed = Number(value);
2982
- return Number.isFinite(parsed) ? parsed : void 0;
2983
- }
2984
- if (value && typeof value === "object" && "id" in value) return parseExperimentId(value.id);
2985
- }
2986
- function experimentIdFromPath(path) {
2987
- const segments = path.split("/").filter(Boolean);
2988
- for (let index = 0; index < segments.length - 1; index += 1) {
2989
- const segment = segments[index]?.toLowerCase();
2990
- if (segment === "experiment" || segment === "experiments") return parseExperimentId(decodeURIComponent(segments[index + 1] ?? ""));
2991
- }
2992
- }
2993
- //#endregion
2994
3222
  //#region src/composables/useExperimentSave.ts
2995
3223
  /** Persists experiment design, analysis, and built-in template preset data through the platform API. */
2996
3224
  function useExperimentSave(options = {}) {
@@ -4428,59 +4656,6 @@ function useProtocolTemplates() {
4428
4656
  };
4429
4657
  }
4430
4658
  //#endregion
4431
- //#region src/composables/useExperimentData.ts
4432
- /** Fetches and normalises experiment output data (tree, table, summary) from the platform API. */
4433
- function useExperimentData(options = {}) {
4434
- const api = useApi({ baseUrl: options.apiBaseUrl });
4435
- const data = ref(null);
4436
- const request = useRequestSyncState("Failed to fetch experiment data");
4437
- const isLoading = request.loading;
4438
- const error = request.error;
4439
- const lastLoadedAt = request.lastLoadedAt;
4440
- let lastExperimentId = null;
4441
- const treeData = computed(() => {
4442
- if (!data.value) return [];
4443
- const tree = data.value.tree_data ?? data.value.treeData;
4444
- return Array.isArray(tree) ? tree : [];
4445
- });
4446
- const tableData = computed(() => {
4447
- if (!data.value) return [];
4448
- const table = data.value.table_data ?? data.value.tableData;
4449
- return Array.isArray(table) ? table : [];
4450
- });
4451
- const summaryData = computed(() => {
4452
- if (!data.value) return null;
4453
- const summary = data.value.summary_data ?? data.value.summaryData;
4454
- if (summary && typeof summary === "object" && "metadata" in summary) return summary;
4455
- return null;
4456
- });
4457
- async function fetchData(experimentId) {
4458
- lastExperimentId = experimentId;
4459
- try {
4460
- data.value = await request.run(() => api.get(`/experiments/${experimentId}/data`), {
4461
- success: "load",
4462
- errorMessage: "Failed to fetch experiment data"
4463
- });
4464
- } catch {
4465
- data.value = null;
4466
- }
4467
- }
4468
- async function refresh() {
4469
- if (lastExperimentId !== null) await fetchData(lastExperimentId);
4470
- }
4471
- return {
4472
- data,
4473
- treeData,
4474
- tableData,
4475
- summaryData,
4476
- isLoading,
4477
- error,
4478
- lastLoadedAt,
4479
- fetch: fetchData,
4480
- refresh
4481
- };
4482
- }
4483
- //#endregion
4484
- export { useTheme as $, useAutoGroup as A, DATE_PRESET_OPTIONS as B, useSampleGroups as C, DEFAULT_COLORS as D, hslToHex as E, usePlatformContext as F, datePresetToISO as G, EXPERIMENT_STATUS_OPTIONS as H, useExperimentSelector as I, getExperimentStatusVariant as J, formatExperimentDate as K, useRequestSyncState as L, useDoseCalculator as M, APP_EXPERIMENT_KEY as N, extractSamplesFromDesignData as O, useAppExperiment as P, useForm as Q, useDebouncedWatch as R, useExpansionSet as S, hexToHsl as T, EXPERIMENT_STATUS_VARIANT_MAP as U, EXPERIMENT_STATUS_LABELS as V, SORT_OPTIONS as W, evaluateCondition as X, resolveExperimentCode as Y, useFormBuilder as Z, useExperimentSave as _, generateDilutionSeries as a, useSortedItems as at, resolveCurrentExperimentId as b, useRackEditor as c, useBioTemplateWorkspace as d, useToast as et, getBioTemplateComponentProps as f, useTemplateCollection as g, useBioTemplateControls as h, DEFAULT_UNITS as i, compareSortValues as it, useWellPlateEditor as j, parseCSV as k, useBioTemplatePresetWorkspace as l, useBioTemplateComponents as m, useProtocolTemplates as n, normalizeSearchQuery as nt, useReagentSeries as o, toBioTemplateComponentPropsByComponent as p, formatExperimentStatus as q, DEFAULT_PRESETS as r, useTextSearch as rt, useGroupAssignment as s, useExperimentData as t, candidateMatchesSearch as tt, useBioTemplatePackWorkspace as u, currentExperimentFromContext as v, deriveShade as w, useScheduleDrag as x, getInjectedPlatformContext as y, useApi as z };
4659
+ export { usePlatformContext as $, parseCSV as A, useRequestSyncState as B, useExpansionSet as C, hslToHex as D, hexToHsl as E, useWellPlateEditor as F, EXPERIMENT_STATUS_OPTIONS as G, useApi as H, useDoseCalculator as I, datePresetToISO as J, EXPERIMENT_STATUS_VARIANT_MAP as K, APP_EXPERIMENT_KEY as L, extractSampleNamesFromDesignData as M, extractSampleOptionsFromDesignData as N, DEFAULT_COLORS as O, unwrapExperimentDesignData as P, resolveExperimentCode as Q, useAppExperiment as R, resolveCurrentExperimentId as S, deriveShade as T, DATE_PRESET_OPTIONS as U, useDebouncedWatch as V, EXPERIMENT_STATUS_LABELS as W, formatExperimentStatus as X, formatExperimentDate as Y, getExperimentStatusVariant as Z, useScheduleDrag as _, useReagentSeries as a, candidateMatchesSearch as at, currentExperimentFromContext as b, useBioTemplatePresetWorkspace as c, compareSortValues as ct, getBioTemplateComponentProps as d, evaluateCondition as et, toBioTemplateComponentPropsByComponent as f, useExperimentSave as g, useTemplateCollection as h, generateDilutionSeries as i, useToast as it, useAutoGroup as j, extractSamplesFromDesignData as k, useBioTemplatePackWorkspace as l, useSortedItems as lt, useBioTemplateControls as m, DEFAULT_PRESETS as n, useForm as nt, useGroupAssignment as o, normalizeSearchQuery as ot, useBioTemplateComponents as p, SORT_OPTIONS as q, DEFAULT_UNITS as r, useTheme as rt, useRackEditor as s, useTextSearch as st, useProtocolTemplates as t, useFormBuilder as tt, useBioTemplateWorkspace as u, useExperimentSamples as v, useSampleGroups as w, getInjectedPlatformContext as x, useExperimentData as y, useExperimentSelector as z };
4485
4660
 
4486
- //# sourceMappingURL=useExperimentData-BbbdI5xT.js.map
4661
+ //# sourceMappingURL=useProtocolTemplates-n6AJqSqv.js.map