@platforma-open/milaboratories.vj-usage.model 2.1.5 → 2.1.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.
package/dist/bundle.js CHANGED
@@ -4,6 +4,113 @@
4
4
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["block-model"] = {}));
5
5
  })(this, (function (exports) { 'use strict';
6
6
 
7
+ /**
8
+ * BlockStorage - Typed storage abstraction for block persistent data.
9
+ *
10
+ * This module provides:
11
+ * - A typed structure for block storage with versioning and plugin support
12
+ * - Utility functions for manipulating storage
13
+ * - Handler interfaces for model-level customization
14
+ *
15
+ * @module block_storage
16
+ */
17
+ // =============================================================================
18
+ // Core Types
19
+ // =============================================================================
20
+ /**
21
+ * Discriminator key for BlockStorage format detection.
22
+ * This unique hash-based key identifies data as BlockStorage vs legacy formats.
23
+ */
24
+ const BLOCK_STORAGE_KEY = '__pl_a7f3e2b9__';
25
+ /**
26
+ * Current BlockStorage schema version.
27
+ * Increment this when the storage structure itself changes (not block state migrations).
28
+ */
29
+ const BLOCK_STORAGE_SCHEMA_VERSION = 'v1';
30
+ /**
31
+ * Default data version for new blocks without migrations.
32
+ * Unique identifier ensures blocks are created via DataModel API.
33
+ */
34
+ const DATA_MODEL_DEFAULT_VERSION = '__pl_v1_d4e8f2a1__';
35
+ /**
36
+ * Type guard to check if a value is a valid BlockStorage object.
37
+ * Checks for the discriminator key and valid schema version.
38
+ */
39
+ function isBlockStorage(value) {
40
+ if (value === null || typeof value !== 'object')
41
+ return false;
42
+ const obj = value;
43
+ const schemaVersion = obj[BLOCK_STORAGE_KEY];
44
+ // Currently only 'v1' is valid, but this allows future versions
45
+ return schemaVersion === 'v1'; // Add more versions as schema evolves
46
+ }
47
+ // =============================================================================
48
+ // Factory Functions
49
+ // =============================================================================
50
+ /**
51
+ * Creates a BlockStorage with the given initial data
52
+ *
53
+ * @param initialData - The initial data value (defaults to empty object)
54
+ * @param version - The initial data version key (defaults to DATA_MODEL_DEFAULT_VERSION)
55
+ * @returns A new BlockStorage instance with discriminator key
56
+ */
57
+ function createBlockStorage(initialData = {}, version = DATA_MODEL_DEFAULT_VERSION) {
58
+ return {
59
+ [BLOCK_STORAGE_KEY]: BLOCK_STORAGE_SCHEMA_VERSION,
60
+ __dataVersion: version,
61
+ __data: initialData,
62
+ };
63
+ }
64
+ /**
65
+ * Normalizes raw storage data to BlockStorage format.
66
+ * If the input is already a BlockStorage, returns it as-is.
67
+ * If the input is legacy format (raw state), wraps it in BlockStorage structure.
68
+ *
69
+ * @param raw - Raw storage data (may be legacy format or BlockStorage)
70
+ * @returns Normalized BlockStorage
71
+ */
72
+ function normalizeBlockStorage(raw) {
73
+ if (isBlockStorage(raw)) {
74
+ const storage = raw;
75
+ return {
76
+ ...storage,
77
+ // Fix for early released version where __dataVersion was a number
78
+ __dataVersion: typeof storage.__dataVersion === 'number'
79
+ ? DATA_MODEL_DEFAULT_VERSION
80
+ : storage.__dataVersion,
81
+ };
82
+ }
83
+ // Legacy format: raw is the state directly
84
+ return createBlockStorage(raw);
85
+ }
86
+ // =============================================================================
87
+ // Data Access & Update Functions
88
+ // =============================================================================
89
+ /**
90
+ * Gets the data from BlockStorage
91
+ *
92
+ * @param storage - The BlockStorage instance
93
+ * @returns The data value
94
+ */
95
+ function getStorageData(storage) {
96
+ return storage.__data;
97
+ }
98
+ /**
99
+ * Updates the data in BlockStorage (immutable)
100
+ *
101
+ * @param storage - The current BlockStorage
102
+ * @param payload - The update payload with operation and value
103
+ * @returns A new BlockStorage with updated data
104
+ */
105
+ function updateStorageData(storage, payload) {
106
+ switch (payload.operation) {
107
+ case 'update-data':
108
+ return { ...storage, __data: payload.value };
109
+ default:
110
+ throw new Error(`Unknown storage operation: ${payload.operation}`);
111
+ }
112
+ }
113
+
7
114
  //
8
115
  // Helpers
9
116
  //
@@ -4690,6 +4797,9 @@
4690
4797
  Table: {
4691
4798
  OrderPriority: 'pl7.app/table/orderPriority'},
4692
4799
  Trace: 'pl7.app/trace',
4800
+ VDJ: {
4801
+ IsAssemblingFeature: 'pl7.app/vdj/isAssemblingFeature',
4802
+ },
4693
4803
  };
4694
4804
  const ValueTypeSchema = z.enum(['Int', 'Long', 'Float', 'Double', 'String']);
4695
4805
  const AnnotationJson = {
@@ -4719,6 +4829,7 @@
4719
4829
  [Annotation.Sequence.IsAnnotation]: z.boolean(),
4720
4830
  [Annotation.Table.OrderPriority]: z.number(),
4721
4831
  [Annotation.Trace]: z.record(z.string(), z.unknown()),
4832
+ [Annotation.VDJ.IsAssemblingFeature]: z.boolean(),
4722
4833
  };
4723
4834
  /// Helper function for reading plain annotation values
4724
4835
  function readAnnotation(spec, key) {
@@ -6094,6 +6205,30 @@
6094
6205
  }
6095
6206
  return result;
6096
6207
  };
6208
+ const countUniqueLabels = (result) => result === undefined ? 0 : new Set(result.map((c) => c.label)).size;
6209
+ // Post-processing: try removing types one by one (lowest importance first) to minimize the label set
6210
+ // Accepts removal if it doesn't decrease the number of unique labels (cardinality)
6211
+ const minimizeTypeSet = (typeSet) => {
6212
+ const initialResult = calculate(typeSet);
6213
+ if (initialResult === undefined) {
6214
+ return typeSet;
6215
+ }
6216
+ const currentCardinality = countUniqueLabels(initialResult);
6217
+ // Get types sorted by importance ascending (lowest first), excluding forced elements
6218
+ const removableSorted = [...typeSet]
6219
+ .filter((t) => !forceTraceElements?.has(t.split('@')[0])
6220
+ && !(ops.includeNativeLabel && t === LabelTypeFull))
6221
+ .sort((a, b) => (importances.get(a) ?? 0) - (importances.get(b) ?? 0));
6222
+ for (const typeToRemove of removableSorted) {
6223
+ const reducedSet = new Set(typeSet);
6224
+ reducedSet.delete(typeToRemove);
6225
+ const candidateResult = calculate(reducedSet);
6226
+ if (candidateResult !== undefined && countUniqueLabels(candidateResult) >= currentCardinality) {
6227
+ typeSet.delete(typeToRemove);
6228
+ }
6229
+ }
6230
+ return typeSet;
6231
+ };
6097
6232
  if (mainTypes.length === 0) {
6098
6233
  if (secondaryTypes.length !== 0)
6099
6234
  throw new Error('Non-empty secondary types list while main types list is empty.');
@@ -6119,16 +6254,20 @@
6119
6254
  if (additionalType >= 0)
6120
6255
  currentSet.add(mainTypes[additionalType]);
6121
6256
  const candidateResult = calculate(currentSet);
6122
- // checking if labels uniquely separate our records
6123
- if (candidateResult !== undefined && new Set(candidateResult.map((c) => c.label)).size === values.length)
6124
- return candidateResult;
6257
+ if (candidateResult !== undefined && countUniqueLabels(candidateResult) === values.length) {
6258
+ minimizeTypeSet(currentSet);
6259
+ return calculate(currentSet);
6260
+ }
6125
6261
  additionalType++;
6126
6262
  if (additionalType >= mainTypes.length) {
6127
6263
  includedTypes++;
6128
6264
  additionalType = includedTypes;
6129
6265
  }
6130
6266
  }
6131
- return calculate(new Set([...mainTypes, ...secondaryTypes]), true);
6267
+ // Fallback: include all types, then try to minimize
6268
+ const fallbackSet = new Set([...mainTypes, ...secondaryTypes]);
6269
+ minimizeTypeSet(fallbackSet);
6270
+ return calculate(fallbackSet, true);
6132
6271
  }
6133
6272
 
6134
6273
  const PCD_PREFIX = 'PColumnData/';
@@ -7231,28 +7370,19 @@
7231
7370
  }
7232
7371
  }
7233
7372
  /** Main entry point to the API available within model lambdas (like outputs, sections, etc..) */
7234
- class RenderCtx {
7373
+ class RenderCtxBase {
7235
7374
  ctx;
7236
7375
  constructor() {
7237
7376
  this.ctx = getCfgRenderCtx();
7238
7377
  }
7239
- _argsCache;
7240
- get args() {
7241
- if (this._argsCache === undefined) {
7242
- const raw = this.ctx.args;
7378
+ _dataCache;
7379
+ get data() {
7380
+ if (this._dataCache === undefined) {
7381
+ const raw = this.ctx.data;
7243
7382
  const value = typeof raw === 'function' ? raw() : raw;
7244
- this._argsCache = { v: JSON.parse(value) };
7383
+ this._dataCache = { v: value ? JSON.parse(value) : {} };
7245
7384
  }
7246
- return this._argsCache.v;
7247
- }
7248
- _uiStateCache;
7249
- get uiState() {
7250
- if (this._uiStateCache === undefined) {
7251
- const raw = this.ctx.uiState;
7252
- const value = typeof raw === 'function' ? raw() : raw;
7253
- this._uiStateCache = { v: value ? JSON.parse(value) : {} };
7254
- }
7255
- return this._uiStateCache.v;
7385
+ return this._dataCache.v;
7256
7386
  }
7257
7387
  // lazy rendering because this feature is rarely used
7258
7388
  _activeArgsCache;
@@ -7363,8 +7493,29 @@
7363
7493
  this.ctx.logError(msg);
7364
7494
  }
7365
7495
  }
7496
+ /** Render context for legacy v1/v2 blocks - provides backward compatibility */
7497
+ class RenderCtxLegacy extends RenderCtxBase {
7498
+ _argsCache;
7499
+ get args() {
7500
+ if (this._argsCache === undefined) {
7501
+ const raw = this.ctx.args;
7502
+ const value = typeof raw === 'function' ? raw() : raw;
7503
+ this._argsCache = { v: JSON.parse(value) };
7504
+ }
7505
+ return this._argsCache.v;
7506
+ }
7507
+ _uiStateCache;
7508
+ get uiState() {
7509
+ if (this._uiStateCache === undefined) {
7510
+ const raw = this.ctx.uiState;
7511
+ const value = typeof raw === 'function' ? raw() : raw;
7512
+ this._uiStateCache = { v: value ? JSON.parse(value) : {} };
7513
+ }
7514
+ return this._uiStateCache.v;
7515
+ }
7516
+ }
7366
7517
 
7367
- var version = "1.51.2";
7518
+ var version = "1.53.3";
7368
7519
 
7369
7520
  const PlatformaSDKVersion = version;
7370
7521
 
@@ -7408,7 +7559,7 @@
7408
7559
  output(key, cfgOrRf, flags = {}) {
7409
7560
  if (typeof cfgOrRf === 'function') {
7410
7561
  const handle = `output#${key}`;
7411
- tryRegisterCallback(handle, () => cfgOrRf(new RenderCtx()));
7562
+ tryRegisterCallback(handle, () => cfgOrRf(new RenderCtxLegacy()));
7412
7563
  return new BlockModel({
7413
7564
  ...this.config,
7414
7565
  outputs: {
@@ -7445,7 +7596,7 @@
7445
7596
  }
7446
7597
  argsValid(cfgOrRf) {
7447
7598
  if (typeof cfgOrRf === 'function') {
7448
- tryRegisterCallback('inputsValid', () => cfgOrRf(new RenderCtx()));
7599
+ tryRegisterCallback('inputsValid', () => cfgOrRf(new RenderCtxLegacy()));
7449
7600
  return new BlockModel({
7450
7601
  ...this.config,
7451
7602
  inputsValid: {
@@ -7466,7 +7617,7 @@
7466
7617
  return this.sections(getImmediate(arrOrCfgOrRf));
7467
7618
  }
7468
7619
  else if (typeof arrOrCfgOrRf === 'function') {
7469
- tryRegisterCallback('sections', () => arrOrCfgOrRf(new RenderCtx()));
7620
+ tryRegisterCallback('sections', () => arrOrCfgOrRf(new RenderCtxLegacy()));
7470
7621
  return new BlockModel({
7471
7622
  ...this.config,
7472
7623
  sections: {
@@ -7484,7 +7635,7 @@
7484
7635
  }
7485
7636
  /** Sets a rendering function to derive block title, shown for the block in the left blocks-overview panel. */
7486
7637
  title(rf) {
7487
- tryRegisterCallback('title', () => rf(new RenderCtx()));
7638
+ tryRegisterCallback('title', () => rf(new RenderCtxLegacy()));
7488
7639
  return new BlockModel({
7489
7640
  ...this.config,
7490
7641
  title: {
@@ -7494,7 +7645,7 @@
7494
7645
  });
7495
7646
  }
7496
7647
  subtitle(rf) {
7497
- tryRegisterCallback('subtitle', () => rf(new RenderCtx()));
7648
+ tryRegisterCallback('subtitle', () => rf(new RenderCtxLegacy()));
7498
7649
  return new BlockModel({
7499
7650
  ...this.config,
7500
7651
  subtitle: {
@@ -7504,7 +7655,7 @@
7504
7655
  });
7505
7656
  }
7506
7657
  tags(rf) {
7507
- tryRegisterCallback('tags', () => rf(new RenderCtx()));
7658
+ tryRegisterCallback('tags', () => rf(new RenderCtxLegacy()));
7508
7659
  return new BlockModel({
7509
7660
  ...this.config,
7510
7661
  tags: {
@@ -7571,7 +7722,10 @@
7571
7722
  if (this.config.initialArgs === undefined)
7572
7723
  throw new Error('Initial arguments not set.');
7573
7724
  const config = {
7725
+ v4: undefined,
7574
7726
  v3: {
7727
+ configVersion: 3,
7728
+ modelAPIVersion: 1,
7575
7729
  sdkVersion: PlatformaSDKVersion,
7576
7730
  renderingMode: this.config.renderingMode,
7577
7731
  initialArgs: this.config.initialArgs,
@@ -7611,6 +7765,259 @@
7611
7765
  }
7612
7766
  }
7613
7767
 
7768
+ /**
7769
+ * BlockStorage VM Integration - Internal module for VM-based storage operations.
7770
+ *
7771
+ * This module auto-registers internal callbacks that the middle layer can invoke
7772
+ * to perform storage transformations. Block developers never interact with these
7773
+ * directly - they only see `state`.
7774
+ *
7775
+ * Registered callbacks (all prefixed with `__pl_` for internal SDK use):
7776
+ * - `__pl_storage_applyUpdate`: (currentStorageJson, payload) => updatedStorageJson
7777
+ * - `__pl_storage_debugView`: (rawStorage) => JSON string with storage debug view
7778
+ * - `__pl_storage_migrate`: (currentStorageJson) => MigrationResult
7779
+ * - `__pl_args_derive`: (storageJson) => ArgsDeriveResult
7780
+ * - `__pl_prerunArgs_derive`: (storageJson) => ArgsDeriveResult
7781
+ *
7782
+ * Callbacks registered by DataModel.registerCallbacks():
7783
+ * - `__pl_data_initial`: () => initial data
7784
+ * - `__pl_data_upgrade`: (versioned) => DataMigrationResult
7785
+ * - `__pl_storage_initial`: () => initial BlockStorage as JSON string
7786
+ *
7787
+ * @module block_storage_vm
7788
+ * @internal
7789
+ */
7790
+ /**
7791
+ * Normalizes raw storage data and extracts state.
7792
+ * Handles all formats:
7793
+ * - New BlockStorage format (has discriminator)
7794
+ * - Legacy V1/V2 format ({ args, uiState })
7795
+ * - Raw V3 state (any other format)
7796
+ *
7797
+ * @param rawStorage - Raw data from blockStorage field (may be JSON string or object)
7798
+ * @returns Object with normalized storage and extracted state
7799
+ */
7800
+ function normalizeStorage(rawStorage) {
7801
+ // Handle undefined/null
7802
+ if (rawStorage === undefined || rawStorage === null) {
7803
+ const storage = createBlockStorage({});
7804
+ return { storage, data: {} };
7805
+ }
7806
+ // Parse JSON string if needed
7807
+ let parsed = rawStorage;
7808
+ if (typeof rawStorage === 'string') {
7809
+ try {
7810
+ parsed = JSON.parse(rawStorage);
7811
+ }
7812
+ catch {
7813
+ // If parsing fails, treat string as the data
7814
+ const storage = createBlockStorage(rawStorage);
7815
+ return { storage, data: rawStorage };
7816
+ }
7817
+ }
7818
+ // Check for BlockStorage format (has discriminator)
7819
+ if (isBlockStorage(parsed)) {
7820
+ const storage = normalizeBlockStorage(parsed);
7821
+ return { storage, data: getStorageData(storage) };
7822
+ }
7823
+ // Check for legacy V1/V2 format: { args, uiState }
7824
+ if (isLegacyModelV1ApiFormat(parsed)) {
7825
+ // For legacy format, the whole object IS the data
7826
+ const storage = createBlockStorage(parsed);
7827
+ return { storage, data: parsed };
7828
+ }
7829
+ // Raw V3 data - wrap it
7830
+ const storage = createBlockStorage(parsed);
7831
+ return { storage, data: parsed };
7832
+ }
7833
+ /**
7834
+ * Applies a state update to existing storage.
7835
+ * Used when setData is called from the frontend.
7836
+ *
7837
+ * @param currentStorageJson - Current storage as JSON string (must be defined)
7838
+ * @param newData - New data from application
7839
+ * @returns Updated storage as JSON string
7840
+ */
7841
+ function applyStorageUpdate(currentStorageJson, payload) {
7842
+ const { storage: currentStorage } = normalizeStorage(currentStorageJson);
7843
+ // Update data while preserving other storage fields (version, plugins)
7844
+ const updatedStorage = updateStorageData(currentStorage, payload);
7845
+ return JSON.stringify(updatedStorage);
7846
+ }
7847
+ /**
7848
+ * Checks if data is in legacy Model API v1 format.
7849
+ * Legacy format has { args, uiState? } at top level without the BlockStorage discriminator.
7850
+ */
7851
+ function isLegacyModelV1ApiFormat(data) {
7852
+ if (data === null || typeof data !== 'object')
7853
+ return false;
7854
+ if (isBlockStorage(data))
7855
+ return false;
7856
+ const obj = data;
7857
+ return 'args' in obj;
7858
+ }
7859
+ // =============================================================================
7860
+ // Auto-register internal callbacks when module is loaded in VM
7861
+ // =============================================================================
7862
+ // Register apply update callback (requires existing storage)
7863
+ tryRegisterCallback('__pl_storage_applyUpdate', (currentStorageJson, payload) => {
7864
+ return applyStorageUpdate(currentStorageJson, payload);
7865
+ });
7866
+ /**
7867
+ * Gets storage debug view from raw storage data.
7868
+ * Returns structured debug info about the storage state.
7869
+ *
7870
+ * @param rawStorage - Raw data from blockStorage field (may be JSON string or object)
7871
+ * @returns JSON string with storage debug view
7872
+ */
7873
+ function getStorageDebugView(rawStorage) {
7874
+ const { storage } = normalizeStorage(rawStorage);
7875
+ const debugView = {
7876
+ dataVersion: storage.__dataVersion,
7877
+ data: storage.__data,
7878
+ };
7879
+ return stringifyJson(debugView);
7880
+ }
7881
+ // Register debug view callback
7882
+ tryRegisterCallback('__pl_storage_debugView', (rawStorage) => {
7883
+ return getStorageDebugView(rawStorage);
7884
+ });
7885
+ /**
7886
+ * Runs storage migration using the DataModel's migrate callback.
7887
+ * This is the main entry point for the middle layer to trigger migrations.
7888
+ *
7889
+ * Uses the '__pl_data_upgrade' callback registered by DataModel.registerCallbacks() which:
7890
+ * - Handles all migration logic internally
7891
+ * - Returns { version, data, warning? } - warning present if reset to initial data
7892
+ *
7893
+ * @param currentStorageJson - Current storage as JSON string (or undefined)
7894
+ * @returns MigrationResult
7895
+ */
7896
+ function migrateStorage(currentStorageJson) {
7897
+ // Get the callback registry context
7898
+ const ctx = tryGetCfgRenderCtx();
7899
+ if (ctx === undefined) {
7900
+ return { error: 'Not in config rendering context' };
7901
+ }
7902
+ // Normalize storage to get current data and version
7903
+ const { storage: currentStorage, data: currentData } = normalizeStorage(currentStorageJson);
7904
+ const currentVersion = currentStorage.__dataVersion;
7905
+ // Helper to create storage with given data and version
7906
+ const createStorageJson = (data, version) => {
7907
+ return JSON.stringify({
7908
+ ...currentStorage,
7909
+ __dataVersion: version,
7910
+ __data: data,
7911
+ });
7912
+ };
7913
+ // Get the migrate callback (registered by DataModel.registerCallbacks())
7914
+ const migrateCallback = ctx.callbackRegistry['__pl_data_upgrade'];
7915
+ if (typeof migrateCallback !== 'function') {
7916
+ return { error: '__pl_data_upgrade callback not found (DataModel not registered)' };
7917
+ }
7918
+ // Call the migrator's migrate function
7919
+ let result;
7920
+ try {
7921
+ result = migrateCallback({ version: currentVersion, data: currentData });
7922
+ }
7923
+ catch (e) {
7924
+ const errorMsg = e instanceof Error ? e.message : String(e);
7925
+ return { error: `migrate() threw: ${errorMsg}` };
7926
+ }
7927
+ // Build info message
7928
+ const info = result.version === currentVersion
7929
+ ? `No migration needed (${currentVersion})`
7930
+ : result.warning
7931
+ ? `Reset to initial data (${result.version})`
7932
+ : `Migrated ${currentVersion}→${result.version}`;
7933
+ return {
7934
+ newStorageJson: createStorageJson(result.data, result.version),
7935
+ info,
7936
+ warn: result.warning,
7937
+ };
7938
+ }
7939
+ // Register migrate callback
7940
+ tryRegisterCallback('__pl_storage_migrate', (currentStorageJson) => {
7941
+ return migrateStorage(currentStorageJson);
7942
+ });
7943
+ /**
7944
+ * Derives args from storage using the registered 'args' callback.
7945
+ * This extracts data from storage and passes it to the block's args() function.
7946
+ *
7947
+ * @param storageJson - Storage as JSON string
7948
+ * @returns ArgsDeriveResult with derived args or error
7949
+ */
7950
+ function deriveArgsFromStorage(storageJson) {
7951
+ const ctx = tryGetCfgRenderCtx();
7952
+ if (ctx === undefined) {
7953
+ return { error: 'Not in config rendering context' };
7954
+ }
7955
+ // Extract data from storage
7956
+ const { data } = normalizeStorage(storageJson);
7957
+ // Get the args callback (registered by BlockModelV3.args())
7958
+ const argsCallback = ctx.callbackRegistry['args'];
7959
+ if (typeof argsCallback !== 'function') {
7960
+ return { error: 'args callback not found' };
7961
+ }
7962
+ // Call the args callback with extracted data
7963
+ try {
7964
+ const result = argsCallback(data);
7965
+ return { value: result };
7966
+ }
7967
+ catch (e) {
7968
+ const errorMsg = e instanceof Error ? e.message : String(e);
7969
+ return { error: `args() threw: ${errorMsg}` };
7970
+ }
7971
+ }
7972
+ // Register args derivation callback
7973
+ tryRegisterCallback('__pl_args_derive', (storageJson) => {
7974
+ return deriveArgsFromStorage(storageJson);
7975
+ });
7976
+ /**
7977
+ * Derives prerunArgs from storage using the registered 'prerunArgs' callback.
7978
+ * Falls back to 'args' callback if 'prerunArgs' is not defined.
7979
+ *
7980
+ * @param storageJson - Storage as JSON string
7981
+ * @returns ArgsDeriveResult with derived prerunArgs or error
7982
+ */
7983
+ function derivePrerunArgsFromStorage(storageJson) {
7984
+ const ctx = tryGetCfgRenderCtx();
7985
+ if (ctx === undefined) {
7986
+ return { error: 'Not in config rendering context' };
7987
+ }
7988
+ // Extract data from storage
7989
+ const { data } = normalizeStorage(storageJson);
7990
+ // Try prerunArgs callback first
7991
+ const prerunArgsCallback = ctx.callbackRegistry['prerunArgs'];
7992
+ if (typeof prerunArgsCallback === 'function') {
7993
+ try {
7994
+ const result = prerunArgsCallback(data);
7995
+ return { value: result };
7996
+ }
7997
+ catch (e) {
7998
+ const errorMsg = e instanceof Error ? e.message : String(e);
7999
+ return { error: `prerunArgs() threw: ${errorMsg}` };
8000
+ }
8001
+ }
8002
+ // Fall back to args callback
8003
+ const argsCallback = ctx.callbackRegistry['args'];
8004
+ if (typeof argsCallback !== 'function') {
8005
+ return { error: 'args callback not found (fallback from missing prerunArgs)' };
8006
+ }
8007
+ try {
8008
+ const result = argsCallback(data);
8009
+ return { value: result };
8010
+ }
8011
+ catch (e) {
8012
+ const errorMsg = e instanceof Error ? e.message : String(e);
8013
+ return { error: `args() threw (fallback): ${errorMsg}` };
8014
+ }
8015
+ }
8016
+ // Register prerunArgs derivation callback
8017
+ tryRegisterCallback('__pl_prerunArgs_derive', (storageJson) => {
8018
+ return derivePrerunArgsFromStorage(storageJson);
8019
+ });
8020
+
7614
8021
  function getAllRelatedColumns(ctx, predicate) {
7615
8022
  // if current block doesn't produce own columns then use all columns from result pool
7616
8023
  const columns = new PColumnCollection();
@@ -7901,13 +8308,32 @@
7901
8308
  errors: z.lazy(() => ErrorShape.array()).optional(),
7902
8309
  });
7903
8310
 
8311
+ function getDefaultBlockLabel(data) {
8312
+ const parts = [];
8313
+ // Add dataset name
8314
+ if (data.datasetLabel) {
8315
+ parts.push(data.datasetLabel);
8316
+ }
8317
+ // Add allele/gene
8318
+ parts.push(data.allele ? 'Allele' : 'Gene');
8319
+ // Add chain info for single-cell datasets
8320
+ if (data.isSingleCell && data.chainLabel) {
8321
+ parts.push(data.chainLabel);
8322
+ }
8323
+ return parts.join(' - ');
8324
+ }
8325
+
7904
8326
  const model = BlockModel.create()
7905
8327
  .withArgs({
8328
+ defaultBlockLabel: getDefaultBlockLabel({
8329
+ allele: false,
8330
+ isSingleCell: false,
8331
+ }),
8332
+ customBlockLabel: '',
7906
8333
  scChain: 'A',
7907
8334
  allele: false,
7908
8335
  })
7909
8336
  .withUiState({
7910
- blockTitle: 'V/J Usage',
7911
8337
  weightedFlag: true,
7912
8338
  vUsagePlotState: {
7913
8339
  title: 'V Usage',
@@ -7920,7 +8346,7 @@
7920
8346
  },
7921
8347
  },
7922
8348
  jUsagePlotState: {
7923
- title: 'V Usage',
8349
+ title: 'J Usage',
7924
8350
  template: 'heatmapClustered',
7925
8351
  currentTab: null,
7926
8352
  layersSettings: {
@@ -7971,7 +8397,8 @@
7971
8397
  return createPFrameForGraphs(ctx, pCols);
7972
8398
  })
7973
8399
  .output('isRunning', (ctx) => ctx.outputs?.getIsReadyOrError() === false)
7974
- .title((ctx) => ctx.uiState?.blockTitle ?? 'V/J Usage')
8400
+ .title(() => 'V/J Usage')
8401
+ .subtitle((ctx) => ctx.args.customBlockLabel || ctx.args.defaultBlockLabel)
7975
8402
  .sections((_) => [
7976
8403
  { type: 'link', href: '/', label: 'V Gene Usage' },
7977
8404
  { type: 'link', href: '/jUsage', label: 'J Gene Usage' },
@@ -7979,6 +8406,7 @@
7979
8406
  ])
7980
8407
  .done(2);
7981
8408
 
8409
+ exports.getDefaultBlockLabel = getDefaultBlockLabel;
7982
8410
  exports.model = model;
7983
8411
 
7984
8412
  }));