@mat3ra/wode 2026.6.19-0 → 2026.7.3-0

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 (24) hide show
  1. package/dist/js/Subworkflow.d.ts +3 -5
  2. package/dist/js/Workflow.d.ts +4 -2
  3. package/dist/js/context/providers/PointsGrid/IGridFormDataManager.js +1 -6
  4. package/dist/js/context/providers/PointsGrid/KGridFormDataManager.js +1 -6
  5. package/dist/js/context/providers/PointsGrid/PointsGridFormDataProvider.d.ts +5 -10
  6. package/dist/js/context/providers/PointsGrid/PointsGridFormDataProvider.js +13 -1
  7. package/dist/js/context/providers/PointsGrid/QGridFormDataManager.js +1 -6
  8. package/dist/js/context/providers/base/ContextProvider.d.ts +4 -0
  9. package/dist/js/context/providers/base/ContextProvider.js +27 -0
  10. package/dist/js/context/providers/by_application/espresso/QEPWXInputDataManager.js +1 -1
  11. package/dist/js/context/providers/index.d.ts +4 -1
  12. package/dist/js/units/ExecutionUnit.d.ts +2 -1
  13. package/dist/js/units/ExecutionUnit.js +15 -5
  14. package/package.json +1 -1
  15. package/src/js/Subworkflow.ts +3 -12
  16. package/src/js/Workflow.ts +4 -2
  17. package/src/js/context/providers/PointsGrid/IGridFormDataManager.ts +1 -11
  18. package/src/js/context/providers/PointsGrid/KGridFormDataManager.ts +1 -11
  19. package/src/js/context/providers/PointsGrid/PointsGridFormDataProvider.ts +26 -2
  20. package/src/js/context/providers/PointsGrid/QGridFormDataManager.ts +1 -11
  21. package/src/js/context/providers/base/ContextProvider.ts +31 -0
  22. package/src/js/context/providers/by_application/espresso/QEPWXInputDataManager.ts +1 -1
  23. package/src/js/context/providers/index.ts +6 -1
  24. package/src/js/units/ExecutionUnit.ts +17 -7
@@ -7,13 +7,11 @@ import { type ComputedEntityMixin } from "@mat3ra/ide/dist/js/compute";
7
7
  import type { Material } from "@mat3ra/made";
8
8
  import { Model, ModelFactory } from "@mat3ra/mode";
9
9
  import type { MetaPropertyHolder } from "@mat3ra/prode";
10
- import type { MaterialExternalContext } from "./context/mixins/MaterialContextMixin";
11
- import type { MaterialsExternalContext } from "./context/mixins/MaterialsContextMixin";
12
- import type { MaterialsSetExternalContext } from "./context/mixins/MaterialsSetContextMixin";
13
- import type { JobExternalContext, WorkflowExternalContext } from "./context/providers/by_application/espresso/QEPWXInputDataManager";
10
+ import type { WorkflowExternalContext } from "./context/providers/by_application/espresso/QEPWXInputDataManager";
14
11
  import { type SubworkflowSchemaMixin } from "./generated/SubworkflowSchemaMixin";
15
12
  import { SubworkflowUnit } from "./units";
16
13
  import type { AnySubworkflowUnit } from "./units/factory";
14
+ import type { WorkflowRenderContext } from "./Workflow";
17
15
  type ConvergenceConfig = {
18
16
  parameter: "N_k" | "N_k_nonuniform";
19
17
  parameterInitial: number | [number, number, number];
@@ -28,7 +26,7 @@ type ConvergenceConfig = {
28
26
  };
29
27
  interface Subworkflow extends DefaultableInMemoryEntity, NamedInMemoryEntity, SubworkflowSchemaMixin, HashedEntity, Omit<ComputedEntityMixin, "compute"> {
30
28
  }
31
- type SubworkflowExternalContext = MaterialExternalContext & MaterialsExternalContext & MaterialsSetExternalContext & WorkflowExternalContext & JobExternalContext;
29
+ type SubworkflowExternalContext = WorkflowRenderContext & WorkflowExternalContext;
32
30
  declare class Subworkflow extends InMemoryEntity implements SubworkflowSchema {
33
31
  private ModelFactory;
34
32
  private applicationInstance;
@@ -21,8 +21,10 @@ import type { WorkflowSchema } from "./workflows/types";
21
21
  interface Workflow extends Defaultable, NamedInMemoryEntity, WorkflowSchemaMixin, Taggable, HashedEntity, ComputedEntityMixin, HasDescription {
22
22
  compute: WorkflowSchema["compute"];
23
23
  }
24
- /** Context passed to Workflow.render() before `workflowHasRelaxation` is injected for subworkflows. */
25
- export type WorkflowRenderContext = MaterialExternalContext & MaterialsExternalContext & MaterialsSetExternalContext & JobExternalContext;
24
+ /** Context passed to Workflow.render(); subworkflows also receive `workflowHasRelaxation`. */
25
+ export type WorkflowRenderContext = MaterialExternalContext & MaterialsExternalContext & MaterialsSetExternalContext & JobExternalContext & {
26
+ scopeGlobal?: Record<string, unknown>;
27
+ };
26
28
  declare class Workflow extends InMemoryEntity implements WorkflowSchema {
27
29
  createDefault: () => Workflow;
28
30
  static readonly defaultConfig: WorkflowSchema;
@@ -3,17 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const JSONSchemasInterface_1 = __importDefault(require("@mat3ra/esse/dist/js/esse/JSONSchemasInterface"));
7
6
  const PointsGridFormDataProvider_1 = __importDefault(require("./PointsGridFormDataProvider"));
8
7
  class IGridFormDataManager extends PointsGridFormDataProvider_1.default {
9
8
  constructor(contextItem, externalContext) {
10
9
  super(contextItem, externalContext, 0.2);
11
10
  this.name = "igrid";
12
- const jsonSchema = JSONSchemasInterface_1.default.getPatchedSchemaById(this.jsonSchemaId, this.jsonSchemaPatchConfig);
13
- if (!jsonSchema) {
14
- throw new Error("Failed to get patched JSON schema");
15
- }
16
- this.jsonSchema = jsonSchema;
11
+ this.jsonSchema = this.buildFormJsonSchema();
17
12
  }
18
13
  static createFromUnitContext(unitContext, externalContext) {
19
14
  const contextItem = this.findContextItem(unitContext, "igrid");
@@ -3,17 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const JSONSchemasInterface_1 = __importDefault(require("@mat3ra/esse/dist/js/esse/JSONSchemasInterface"));
7
6
  const PointsGridFormDataProvider_1 = __importDefault(require("./PointsGridFormDataProvider"));
8
7
  class KGridFormDataManager extends PointsGridFormDataProvider_1.default {
9
8
  constructor(contextItem, externalContext) {
10
9
  super(contextItem, externalContext, 1);
11
10
  this.name = "kgrid";
12
- const jsonSchema = JSONSchemasInterface_1.default.getPatchedSchemaById(this.jsonSchemaId, this.jsonSchemaPatchConfig);
13
- if (!jsonSchema) {
14
- throw new Error("Failed to get patched JSON schema");
15
- }
16
- this.jsonSchema = jsonSchema;
11
+ this.jsonSchema = this.buildFormJsonSchema();
17
12
  }
18
13
  static createFromUnitContext(unitContext, externalContext) {
19
14
  const contextItem = this.findContextItem(unitContext, "kgrid");
@@ -36,16 +36,6 @@ declare abstract class PointsGridFormDataProvider<N extends Schema["name"]> exte
36
36
  getData(): Data;
37
37
  getDefaultData(): PointsGridDataProviderSchema;
38
38
  protected get jsonSchemaPatchConfig(): {
39
- dimensions: {
40
- default?: any[] | undefined;
41
- type: string;
42
- items: {
43
- default?: string | number | readonly number[] | readonly string[] | undefined;
44
- type: string;
45
- };
46
- minItems: number;
47
- maxItems: number;
48
- };
49
39
  shifts: {
50
40
  default?: any[] | undefined;
51
41
  type: string;
@@ -113,6 +103,11 @@ declare abstract class PointsGridFormDataProvider<N extends Schema["name"]> exte
113
103
  };
114
104
  };
115
105
  };
106
+ /**
107
+ * Form schema for RJSF. Replaces ESSE `dimensions.anyOf` (number[] | string[]) with a single
108
+ * array type — patch merge cannot remove `anyOf`, which makes RJSF render a branch picker.
109
+ */
110
+ protected buildFormJsonSchema(): JSONSchema7;
116
111
  /** Prefer persisted `data` — `setData` runs before React re-inits the provider on render. */
117
112
  private get preferGridMetricForUi();
118
113
  get uiSchema(): {
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const constants_1 = require("@mat3ra/code/dist/js/constants");
7
7
  const math_1 = require("@mat3ra/code/dist/js/math");
8
+ const JSONSchemasInterface_1 = __importDefault(require("@mat3ra/esse/dist/js/esse/JSONSchemasInterface"));
8
9
  const made_1 = require("@mat3ra/made");
9
10
  const MaterialContextMixin_1 = __importDefault(require("../../mixins/MaterialContextMixin"));
10
11
  const JSONSchemaFormDataProvider_1 = __importDefault(require("../base/JSONSchemaFormDataProvider"));
@@ -112,7 +113,6 @@ class PointsGridFormDataProvider extends JSONSchemaFormDataProvider_1.default {
112
113
  };
113
114
  const gridMetricType = ((_a = this.data) === null || _a === void 0 ? void 0 : _a.gridMetricType) || this.defaultMetric.type;
114
115
  return {
115
- dimensions: vector(this.defaultDimensions, this.isUsingJinjaVariables),
116
116
  shifts: vector(defaultShifts),
117
117
  reciprocalVectorRatios: vector(this.reciprocalVectorRatios),
118
118
  gridMetricType: { default: this.defaultMetric.type },
@@ -158,6 +158,18 @@ class PointsGridFormDataProvider extends JSONSchemaFormDataProvider_1.default {
158
158
  },
159
159
  };
160
160
  }
161
+ /**
162
+ * Form schema for RJSF. Replaces ESSE `dimensions.anyOf` (number[] | string[]) with a single
163
+ * array type — patch merge cannot remove `anyOf`, which makes RJSF render a branch picker.
164
+ */
165
+ buildFormJsonSchema() {
166
+ const jsonSchema = JSONSchemasInterface_1.default.getPatchedSchemaById(this.jsonSchemaId, this.jsonSchemaPatchConfig);
167
+ if (!(jsonSchema === null || jsonSchema === void 0 ? void 0 : jsonSchema.properties)) {
168
+ throw new Error("Failed to get patched JSON schema");
169
+ }
170
+ jsonSchema.properties.dimensions = vector(this.defaultDimensions, this.isUsingJinjaVariables);
171
+ return jsonSchema;
172
+ }
161
173
  /** Prefer persisted `data` — `setData` runs before React re-inits the provider on render. */
162
174
  get preferGridMetricForUi() {
163
175
  var _a, _b;
@@ -3,17 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- const JSONSchemasInterface_1 = __importDefault(require("@mat3ra/esse/dist/js/esse/JSONSchemasInterface"));
7
6
  const PointsGridFormDataProvider_1 = __importDefault(require("./PointsGridFormDataProvider"));
8
7
  class QGridFormDataManager extends PointsGridFormDataProvider_1.default {
9
8
  constructor(contextItem, externalContext) {
10
9
  super(contextItem, externalContext, 5);
11
10
  this.name = "qgrid";
12
- const jsonSchema = JSONSchemasInterface_1.default.getPatchedSchemaById(this.jsonSchemaId, this.jsonSchemaPatchConfig);
13
- if (!jsonSchema) {
14
- throw new Error("Failed to get patched JSON schema");
15
- }
16
- this.jsonSchema = jsonSchema;
11
+ this.jsonSchema = this.buildFormJsonSchema();
17
12
  }
18
13
  static createFromUnitContext(unitContext, externalContext) {
19
14
  const contextItem = this.findContextItem(unitContext, "qgrid");
@@ -66,6 +66,10 @@ DataForRendering = S["data"]> {
66
66
  getDataForRendering(): DataForRendering;
67
67
  getContextItemData(): S;
68
68
  getContextItemDataForRendering(): ContextItemForRendering<S, DataForRendering>;
69
+ /**
70
+ * Resolves Jinja placeholders in persisted context `data` from `scope.global`.
71
+ */
72
+ renderContext(scopeGlobal: Record<string, unknown>): boolean;
69
73
  /**
70
74
  * Helper method to find a context item from a unit context array by name.
71
75
  * Returns a partial schema object that can be safely passed to constructors.
@@ -1,6 +1,18 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ const standata_1 = require("@mat3ra/standata");
3
7
  const utils_1 = require("@mat3ra/utils");
8
+ const nunjucks_1 = __importDefault(require("nunjucks"));
9
+ const nunjucksEnvironment = (0, standata_1.setupNunjucksEnvironment)(new nunjucks_1.default.Environment());
10
+ const parseNumericStrings = (_key, value) => {
11
+ if (typeof value === "string" && value.trim() !== "" && !Number.isNaN(Number(value))) {
12
+ return Number(value);
13
+ }
14
+ return value;
15
+ };
4
16
  /**
5
17
  * Context providers expose three data layers. Keep them separate.
6
18
  *
@@ -75,6 +87,21 @@ class ContextProvider {
75
87
  data: this.getDataForRendering(),
76
88
  };
77
89
  }
90
+ /**
91
+ * Resolves Jinja placeholders in persisted context `data` from `scope.global`.
92
+ */
93
+ renderContext(scopeGlobal) {
94
+ const data = this.getData();
95
+ const dataJson = JSON.stringify(data);
96
+ const renderedJson = nunjucksEnvironment.renderString(dataJson, scopeGlobal);
97
+ const resolved = JSON.parse(renderedJson, parseNumericStrings);
98
+ if (JSON.stringify(data) === JSON.stringify(resolved)) {
99
+ return false;
100
+ }
101
+ this.setData(resolved);
102
+ this.setIsEdited(true);
103
+ return true;
104
+ }
78
105
  /**
79
106
  * Helper method to find a context item from a unit context array by name.
80
107
  * Returns a partial schema object that can be safely passed to constructors.
@@ -52,7 +52,7 @@ class QEPWXInputDataManager extends JSONSchemaDataProvider_1.default {
52
52
  const pseudo = (((_b = this.methodData) === null || _b === void 0 ? void 0 : _b.pseudo) || []).find((p) => p.element === symbolWithoutLabel);
53
53
  return {
54
54
  X: `${symbolWithoutLabel}${label}`,
55
- Mass_X: periodic_table_js_1.PERIODIC_TABLE[symbol].atomic_mass,
55
+ Mass_X: periodic_table_js_1.PERIODIC_TABLE[symbolWithoutLabel].atomic_mass,
56
56
  PseudoPot_X: (pseudo === null || pseudo === void 0 ? void 0 : pseudo.filename) || path_1.default.basename((pseudo === null || pseudo === void 0 ? void 0 : pseudo.path) || ""),
57
57
  };
58
58
  });
@@ -65,11 +65,14 @@ export type AssignmentContext = Record<AssignmentUnitSchema["operand"], Assignme
65
65
  export type SubworkflowContext = {
66
66
  subworkflowContext: AssignmentContext;
67
67
  };
68
+ export type ScopeGlobalExternalContext = {
69
+ scopeGlobal?: Record<string, unknown>;
70
+ };
68
71
  /**
69
72
  * External context type used by ExecutionUnitInput when creating providers.
70
73
  * This type is always expected to be present when providers are instantiated.
71
74
  */
72
- export type ExternalContext = ApplicationExternalContext & WorkflowExternalContext & JobExternalContext & MaterialsExternalContext & MethodDataExternalContext & MaterialsSetExternalContext & MaterialExternalContext & JinjaExternalContext & SubworkflowContext;
75
+ export type ExternalContext = ApplicationExternalContext & WorkflowExternalContext & JobExternalContext & MaterialsExternalContext & MethodDataExternalContext & MaterialsSetExternalContext & MaterialExternalContext & JinjaExternalContext & SubworkflowContext & ScopeGlobalExternalContext;
73
76
  /**
74
77
  * Type for provider names as they appear in templates.
75
78
  */
@@ -43,8 +43,9 @@ declare class ExecutionUnit extends ExecutionUnit_base implements Schema {
43
43
  render(externalContext: ExternalContext, convergence?: ConvergenceParameter): void;
44
44
  private getContextProvidersInstances;
45
45
  savePersistentContext(): void;
46
+ renderContext(scopeGlobal: Record<string, unknown>): void;
46
47
  saveRenderingContext(externalContext: ExternalContext): void;
47
- saveContext(externalContext: ExternalContext): void;
48
+ saveContext({ scopeGlobal, ...externalContext }: ExternalContext): void;
48
49
  getHashObject(): {
49
50
  application: {
50
51
  _id?: string;
@@ -130,7 +130,7 @@ class ExecutionUnit extends BaseUnit_1.default {
130
130
  .flat()),
131
131
  ];
132
132
  // TODO: kgrid should be abstracted and selected by user
133
- const parameterToContextProviderMap = {
133
+ const parameterToProviderMap = {
134
134
  N_k: "kgrid",
135
135
  N_k_nonuniform: "kgrid",
136
136
  };
@@ -139,8 +139,7 @@ class ExecutionUnit extends BaseUnit_1.default {
139
139
  return (0, providers_1.createProvider)(name, this.context, externalContext);
140
140
  })
141
141
  .map((provider) => {
142
- if (convergence &&
143
- provider.name === parameterToContextProviderMap[convergence.name]) {
142
+ if (convergence && provider.name === parameterToProviderMap[convergence.name]) {
144
143
  provider.applyConvergenceParameter(convergence);
145
144
  }
146
145
  return provider;
@@ -150,17 +149,28 @@ class ExecutionUnit extends BaseUnit_1.default {
150
149
  const persistentItems = this.contextProvidersInstances.map((p) => p.getContextItemData());
151
150
  this.context = persistentItems.filter((c) => c.isEdited);
152
151
  }
152
+ renderContext(scopeGlobal) {
153
+ this.contextProvidersInstances.forEach((provider) => {
154
+ provider.renderContext(scopeGlobal);
155
+ });
156
+ }
153
157
  saveRenderingContext(externalContext) {
158
+ // scopeGlobal resolves provider data only; do not pass it to input Jinja templates.
159
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- omitted from Jinja context
160
+ const { scopeGlobal, ...renderingExternalContext } = externalContext;
154
161
  const renderingItems = this.contextProvidersInstances.map((p) => p.getContextItemDataForRendering());
155
162
  this.renderingContext = {
156
163
  ...Object.fromEntries(renderingItems.map((context) => [context.name, context.data])),
157
- ...externalContext,
164
+ ...renderingExternalContext,
158
165
  };
159
166
  this.input = this.inputInstances.map((input) => {
160
167
  return input.render(this.renderingContext).toJSON();
161
168
  });
162
169
  }
163
- saveContext(externalContext) {
170
+ saveContext({ scopeGlobal, ...externalContext }) {
171
+ if (scopeGlobal) {
172
+ this.renderContext(scopeGlobal);
173
+ }
164
174
  this.savePersistentContext();
165
175
  this.saveRenderingContext(externalContext);
166
176
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mat3ra/wode",
3
- "version": "2026.6.19-0",
3
+ "version": "2026.7.3-0",
4
4
  "description": "WOrkflow DEfinitions",
5
5
  "scripts": {
6
6
  "test": "mocha --recursive --bail --require ts-node/register 'tests/js/**/*.ts'",
@@ -20,14 +20,8 @@ import type { MetaPropertyHolder } from "@mat3ra/prode";
20
20
  import { ApplicationRegistry, setUnitLinks } from "@mat3ra/standata";
21
21
  import { Utils } from "@mat3ra/utils";
22
22
 
23
- import type { MaterialExternalContext } from "./context/mixins/MaterialContextMixin";
24
- import type { MaterialsExternalContext } from "./context/mixins/MaterialsContextMixin";
25
- import type { MaterialsSetExternalContext } from "./context/mixins/MaterialsSetContextMixin";
26
23
  import type { AssignmentContext, ExternalContext } from "./context/providers";
27
- import type {
28
- JobExternalContext,
29
- WorkflowExternalContext,
30
- } from "./context/providers/by_application/espresso/QEPWXInputDataManager";
24
+ import type { WorkflowExternalContext } from "./context/providers/by_application/espresso/QEPWXInputDataManager";
31
25
  import { createConvergenceParameter } from "./convergence/factory";
32
26
  import { UnitTag, UnitType } from "./enums";
33
27
  import {
@@ -36,6 +30,7 @@ import {
36
30
  } from "./generated/SubworkflowSchemaMixin";
37
31
  import { AssignmentUnit, ConditionUnit, SubworkflowUnit, UnitFactory } from "./units";
38
32
  import type { AnySubworkflowUnit } from "./units/factory";
33
+ import type { WorkflowRenderContext } from "./Workflow";
39
34
 
40
35
  type ConvergenceConfig = {
41
36
  parameter: "N_k" | "N_k_nonuniform";
@@ -57,11 +52,7 @@ interface Subworkflow
57
52
  HashedEntity,
58
53
  Omit<ComputedEntityMixin, "compute"> {}
59
54
 
60
- type SubworkflowExternalContext = MaterialExternalContext &
61
- MaterialsExternalContext &
62
- MaterialsSetExternalContext &
63
- WorkflowExternalContext &
64
- JobExternalContext;
55
+ type SubworkflowExternalContext = WorkflowRenderContext & WorkflowExternalContext;
65
56
 
66
57
  class Subworkflow extends InMemoryEntity implements SubworkflowSchema {
67
58
  private ModelFactory: typeof ModelFactory;
@@ -55,11 +55,13 @@ interface Workflow
55
55
  compute: WorkflowSchema["compute"];
56
56
  }
57
57
 
58
- /** Context passed to Workflow.render() before `workflowHasRelaxation` is injected for subworkflows. */
58
+ /** Context passed to Workflow.render(); subworkflows also receive `workflowHasRelaxation`. */
59
59
  export type WorkflowRenderContext = MaterialExternalContext &
60
60
  MaterialsExternalContext &
61
61
  MaterialsSetExternalContext &
62
- JobExternalContext;
62
+ JobExternalContext & {
63
+ scopeGlobal?: Record<string, unknown>;
64
+ };
63
65
 
64
66
  class Workflow extends InMemoryEntity implements WorkflowSchema {
65
67
  declare createDefault: () => Workflow;
@@ -1,4 +1,3 @@
1
- import JSONSchemasInterface from "@mat3ra/esse/dist/js/esse/JSONSchemasInterface";
2
1
  import type { GridContextItemSchema } from "@mat3ra/esse/dist/js/types";
3
2
  import type { JSONSchema7 } from "json-schema";
4
3
 
@@ -16,16 +15,7 @@ export default class IGridFormDataManager extends PointsGridFormDataProvider<Nam
16
15
  constructor(contextItem: Partial<Schema>, externalContext: ExternalContext) {
17
16
  super(contextItem, externalContext, 0.2);
18
17
 
19
- const jsonSchema = JSONSchemasInterface.getPatchedSchemaById(
20
- this.jsonSchemaId,
21
- this.jsonSchemaPatchConfig,
22
- );
23
-
24
- if (!jsonSchema) {
25
- throw new Error("Failed to get patched JSON schema");
26
- }
27
-
28
- this.jsonSchema = jsonSchema;
18
+ this.jsonSchema = this.buildFormJsonSchema();
29
19
  }
30
20
 
31
21
  static createFromUnitContext(unitContext: UnitContext, externalContext: ExternalContext) {
@@ -1,4 +1,3 @@
1
- import JSONSchemasInterface from "@mat3ra/esse/dist/js/esse/JSONSchemasInterface";
2
1
  import type { GridContextItemSchema } from "@mat3ra/esse/dist/js/types";
3
2
  import type { JSONSchema7 } from "json-schema";
4
3
 
@@ -17,16 +16,7 @@ export default class KGridFormDataManager extends PointsGridFormDataProvider<Nam
17
16
  constructor(contextItem: Partial<Schema>, externalContext: ExternalContext) {
18
17
  super(contextItem, externalContext, 1);
19
18
 
20
- const jsonSchema = JSONSchemasInterface.getPatchedSchemaById(
21
- this.jsonSchemaId,
22
- this.jsonSchemaPatchConfig,
23
- );
24
-
25
- if (!jsonSchema) {
26
- throw new Error("Failed to get patched JSON schema");
27
- }
28
-
29
- this.jsonSchema = jsonSchema;
19
+ this.jsonSchema = this.buildFormJsonSchema();
30
20
  }
31
21
 
32
22
  static createFromUnitContext(unitContext: UnitContext, externalContext: ExternalContext) {
@@ -1,13 +1,14 @@
1
1
  import { Units } from "@mat3ra/code/dist/js/constants";
2
2
  import { math as codeJSMath } from "@mat3ra/code/dist/js/math";
3
3
  import type { Constructor } from "@mat3ra/code/dist/js/utils/types";
4
+ import JSONSchemasInterface from "@mat3ra/esse/dist/js/esse/JSONSchemasInterface";
4
5
  import type {
5
6
  GridContextItemSchema,
6
7
  PointsGridDataProviderSchema,
7
8
  Vector3DSchema,
8
9
  } from "@mat3ra/esse/dist/js/types";
9
10
  import { type ReciprocalLattice, Made } from "@mat3ra/made";
10
- import type { JSONSchema7 } from "json-schema";
11
+ import type { JSONSchema7, JSONSchema7Definition } from "json-schema";
11
12
 
12
13
  import materialContextMixin, {
13
14
  type MaterialContextMixin,
@@ -81,6 +82,8 @@ abstract class PointsGridFormDataProvider<
81
82
  value: number;
82
83
  };
83
84
 
85
+ // Assigned in subclass constructors via buildFormJsonSchema() — not in this constructor:
86
+ // jsonSchemaPatchConfig uses this.name, which is only set after super() returns.
84
87
  abstract readonly jsonSchema: JSONSchema7;
85
88
 
86
89
  constructor(contextItem: Partial<Schema>, externalContext: ExternalContext, divisor: number) {
@@ -186,7 +189,6 @@ abstract class PointsGridFormDataProvider<
186
189
  const gridMetricType = this.data?.gridMetricType || this.defaultMetric.type;
187
190
 
188
191
  return {
189
- dimensions: vector(this.defaultDimensions, this.isUsingJinjaVariables),
190
192
  shifts: vector(defaultShifts),
191
193
  reciprocalVectorRatios: vector(this.reciprocalVectorRatios),
192
194
  gridMetricType: { default: this.defaultMetric.type },
@@ -235,6 +237,28 @@ abstract class PointsGridFormDataProvider<
235
237
  };
236
238
  }
237
239
 
240
+ /**
241
+ * Form schema for RJSF. Replaces ESSE `dimensions.anyOf` (number[] | string[]) with a single
242
+ * array type — patch merge cannot remove `anyOf`, which makes RJSF render a branch picker.
243
+ */
244
+ protected buildFormJsonSchema(): JSONSchema7 {
245
+ const jsonSchema = JSONSchemasInterface.getPatchedSchemaById(
246
+ this.jsonSchemaId,
247
+ this.jsonSchemaPatchConfig,
248
+ );
249
+
250
+ if (!jsonSchema?.properties) {
251
+ throw new Error("Failed to get patched JSON schema");
252
+ }
253
+
254
+ jsonSchema.properties.dimensions = vector(
255
+ this.defaultDimensions,
256
+ this.isUsingJinjaVariables,
257
+ ) as JSONSchema7Definition;
258
+
259
+ return jsonSchema;
260
+ }
261
+
238
262
  /** Prefer persisted `data` — `setData` runs before React re-inits the provider on render. */
239
263
  private get preferGridMetricForUi() {
240
264
  return this.data?.preferGridMetric ?? this.preferGridMetric;
@@ -1,4 +1,3 @@
1
- import JSONSchemasInterface from "@mat3ra/esse/dist/js/esse/JSONSchemasInterface";
2
1
  import type { GridContextItemSchema } from "@mat3ra/esse/dist/js/types";
3
2
  import type { JSONSchema7 } from "json-schema";
4
3
 
@@ -16,16 +15,7 @@ export default class QGridFormDataManager extends PointsGridFormDataProvider<Nam
16
15
  constructor(contextItem: Partial<Schema>, externalContext: ExternalContext) {
17
16
  super(contextItem, externalContext, 5);
18
17
 
19
- const jsonSchema = JSONSchemasInterface.getPatchedSchemaById(
20
- this.jsonSchemaId,
21
- this.jsonSchemaPatchConfig,
22
- );
23
-
24
- if (!jsonSchema) {
25
- throw new Error("Failed to get patched JSON schema");
26
- }
27
-
28
- this.jsonSchema = jsonSchema;
18
+ this.jsonSchema = this.buildFormJsonSchema();
29
19
  }
30
20
 
31
21
  static createFromUnitContext(unitContext: UnitContext, externalContext: ExternalContext) {
@@ -1,5 +1,7 @@
1
1
  import { type ContextItemSchema } from "@mat3ra/esse/dist/js/types";
2
+ import { setupNunjucksEnvironment } from "@mat3ra/standata";
2
3
  import { Utils } from "@mat3ra/utils";
4
+ import nunjucks from "nunjucks";
3
5
 
4
6
  export type UnitContext = ContextItemSchema[];
5
7
  export type ContextName = ContextItemSchema["name"];
@@ -28,6 +30,16 @@ export type EntityName = "unit" | "subworkflow";
28
30
  /** Minimal bound for provider external context; the full contract is ExternalContext in context/providers/index.ts */
29
31
  export type BaseExternalContext = object;
30
32
 
33
+ const nunjucksEnvironment = setupNunjucksEnvironment(new nunjucks.Environment());
34
+
35
+ const parseNumericStrings = (_key: string, value: unknown) => {
36
+ if (typeof value === "string" && value.trim() !== "" && !Number.isNaN(Number(value))) {
37
+ return Number(value);
38
+ }
39
+
40
+ return value;
41
+ };
42
+
31
43
  /**
32
44
  * Context providers expose three data layers. Keep them separate.
33
45
  *
@@ -134,6 +146,25 @@ abstract class ContextProvider<
134
146
  };
135
147
  }
136
148
 
149
+ /**
150
+ * Resolves Jinja placeholders in persisted context `data` from `scope.global`.
151
+ */
152
+ renderContext(scopeGlobal: Record<string, unknown>): boolean {
153
+ const data = this.getData();
154
+ const dataJson = JSON.stringify(data);
155
+ const renderedJson = nunjucksEnvironment.renderString(dataJson, scopeGlobal);
156
+ const resolved = JSON.parse(renderedJson, parseNumericStrings) as S["data"];
157
+
158
+ if (JSON.stringify(data) === JSON.stringify(resolved)) {
159
+ return false;
160
+ }
161
+
162
+ this.setData(resolved);
163
+ this.setIsEdited(true);
164
+
165
+ return true;
166
+ }
167
+
137
168
  /**
138
169
  * Helper method to find a context item from a unit context array by name.
139
170
  * Returns a partial schema object that can be safely passed to constructors.
@@ -117,7 +117,7 @@ class QEPWXInputDataManager extends JSONSchemaDataProvider<Schema, ExternalConte
117
117
  );
118
118
  return {
119
119
  X: `${symbolWithoutLabel}${label}`,
120
- Mass_X: PERIODIC_TABLE[symbol].atomic_mass,
120
+ Mass_X: PERIODIC_TABLE[symbolWithoutLabel].atomic_mass,
121
121
  PseudoPot_X: pseudo?.filename || path.basename(pseudo?.path || ""),
122
122
  };
123
123
  });
@@ -77,6 +77,10 @@ export type SubworkflowContext = {
77
77
  subworkflowContext: AssignmentContext;
78
78
  };
79
79
 
80
+ export type ScopeGlobalExternalContext = {
81
+ scopeGlobal?: Record<string, unknown>;
82
+ };
83
+
80
84
  /**
81
85
  * External context type used by ExecutionUnitInput when creating providers.
82
86
  * This type is always expected to be present when providers are instantiated.
@@ -89,7 +93,8 @@ export type ExternalContext = ApplicationExternalContext &
89
93
  MaterialsSetExternalContext &
90
94
  MaterialExternalContext &
91
95
  JinjaExternalContext &
92
- SubworkflowContext;
96
+ SubworkflowContext &
97
+ ScopeGlobalExternalContext;
93
98
 
94
99
  /**
95
100
  * Type for provider names as they appear in templates.
@@ -207,7 +207,7 @@ class ExecutionUnit extends (BaseUnit as Base) implements Schema {
207
207
  ];
208
208
 
209
209
  // TODO: kgrid should be abstracted and selected by user
210
- const parameterToContextProviderMap = {
210
+ const parameterToProviderMap = {
211
211
  N_k: "kgrid",
212
212
  N_k_nonuniform: "kgrid",
213
213
  } as const;
@@ -217,10 +217,7 @@ class ExecutionUnit extends (BaseUnit as Base) implements Schema {
217
217
  return createProvider(name, this.context, externalContext);
218
218
  })
219
219
  .map((provider) => {
220
- if (
221
- convergence &&
222
- provider.name === parameterToContextProviderMap[convergence.name]
223
- ) {
220
+ if (convergence && provider.name === parameterToProviderMap[convergence.name]) {
224
221
  provider.applyConvergenceParameter(convergence);
225
222
  }
226
223
  return provider;
@@ -232,20 +229,33 @@ class ExecutionUnit extends (BaseUnit as Base) implements Schema {
232
229
  this.context = persistentItems.filter((c) => c.isEdited);
233
230
  }
234
231
 
232
+ renderContext(scopeGlobal: Record<string, unknown>) {
233
+ this.contextProvidersInstances.forEach((provider) => {
234
+ provider.renderContext(scopeGlobal);
235
+ });
236
+ }
237
+
235
238
  saveRenderingContext(externalContext: ExternalContext) {
239
+ // scopeGlobal resolves provider data only; do not pass it to input Jinja templates.
240
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- omitted from Jinja context
241
+ const { scopeGlobal, ...renderingExternalContext } = externalContext;
236
242
  const renderingItems = this.contextProvidersInstances.map((p) =>
237
243
  p.getContextItemDataForRendering(),
238
244
  );
239
245
  this.renderingContext = {
240
246
  ...Object.fromEntries(renderingItems.map((context) => [context.name, context.data])),
241
- ...externalContext,
247
+ ...renderingExternalContext,
242
248
  };
243
249
  this.input = this.inputInstances.map((input) => {
244
250
  return input.render(this.renderingContext).toJSON();
245
251
  });
246
252
  }
247
253
 
248
- saveContext(externalContext: ExternalContext) {
254
+ saveContext({ scopeGlobal, ...externalContext }: ExternalContext) {
255
+ if (scopeGlobal) {
256
+ this.renderContext(scopeGlobal);
257
+ }
258
+
249
259
  this.savePersistentContext();
250
260
  this.saveRenderingContext(externalContext);
251
261
  }