@field/cascade 0.3.0 → 0.3.1

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 (43) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/assets/{CodeEditor-BADFbTJS.js → CodeEditor-D22fVi-4.js} +4 -4
  3. package/dist/assets/{CodePanel-faK6PRfz.js → CodePanel-DPNEYfcR.js} +2 -2
  4. package/dist/assets/{CustomNodeDialog-DhlKuviy.js → CustomNodeDialog-DeHCAvgb.js} +1 -1
  5. package/dist/assets/{ProjectSettings-D6a6kjqR.js → ProjectSettings-DWpVtWEx.js} +1 -1
  6. package/dist/assets/{SettingsDialog-DJZdIvLC.js → SettingsDialog-za00IS3L.js} +1 -1
  7. package/dist/assets/{VersionHistoryDialog-C1X3HOhc.js → VersionHistoryDialog-DTMGhI0_.js} +1 -1
  8. package/dist/assets/{core-CiuiuWZi.js → core-JsyKMO1k.js} +1 -1
  9. package/dist/assets/{cssMode-D-NwZSMJ.js → cssMode-emdRheFK.js} +1 -1
  10. package/dist/assets/{editor.api-DT3WEvBd.js → editor.api-xHX-fMSo.js} +1 -1
  11. package/dist/assets/{freemarker2-eKNYQaiz.js → freemarker2-C3M8_Gs3.js} +1 -1
  12. package/dist/assets/geo-BAR6lQp5.js +1 -0
  13. package/dist/assets/{handlebars-C2w1ggK2.js → handlebars-CBMurtFk.js} +1 -1
  14. package/dist/assets/{html-DQb41jyo.js → html-xbLQwknw.js} +1 -1
  15. package/dist/assets/{htmlMode-IhNlh6Lv.js → htmlMode-2wKdDTon.js} +1 -1
  16. package/dist/assets/{image-DMCXBY-_.js → image-i1j6nWKe.js} +1 -1
  17. package/dist/assets/{index-HTCN44kj.js → index-CMcH8AgP.js} +35 -35
  18. package/dist/assets/{javascript-BIT6jMLC.js → javascript-DJaOo_2I.js} +1 -1
  19. package/dist/assets/{jsonMode-CNfyB1ro.js → jsonMode-Bheas8_0.js} +1 -1
  20. package/dist/assets/{liquid-CmSmJmOG.js → liquid-BCj_BGbM.js} +1 -1
  21. package/dist/assets/{lspLanguageFeatures-BqMm3uMP.js → lspLanguageFeatures-BfIsx_xz.js} +1 -1
  22. package/dist/assets/{mdx-6r6YgOFw.js → mdx-DwiQDiVO.js} +1 -1
  23. package/dist/assets/{python-x1H0TQuI.js → python-BjrB5nqj.js} +1 -1
  24. package/dist/assets/{razor-Dzl1vZFm.js → razor-CfoBmGDs.js} +1 -1
  25. package/dist/assets/{tsMode-n6DMMe3a.js → tsMode-BcEoMSxp.js} +1 -1
  26. package/dist/assets/{typescript-ziEz65xN.js → typescript-DIg7TEmQ.js} +1 -1
  27. package/dist/assets/{workers-D-qUdp-r.js → workers-vXYuwtPw.js} +1 -1
  28. package/dist/assets/{xml-DMZjt5lE.js → xml-DexLQIQy.js} +1 -1
  29. package/dist/assets/{yaml-DFe5qZSO.js → yaml-DUnD_1dE.js} +1 -1
  30. package/dist/cli/index.js +317 -49
  31. package/dist/cli/runtime/gpu.ts +76 -0
  32. package/dist/contracts/capabilities.d.ts +69 -6
  33. package/dist/contracts/definition.d.ts +21 -1
  34. package/dist/contracts/schema.js +8 -5
  35. package/dist/contracts/validate.js +11 -2
  36. package/dist/gpu.d.ts +71 -0
  37. package/dist/gpu.js +53 -0
  38. package/dist/index.html +1 -1
  39. package/dist/runtime/runtime.d.ts +23 -3
  40. package/dist/runtime/runtime.js +118 -24
  41. package/dist/runtime/types.d.ts +3 -3
  42. package/package.json +5 -1
  43. package/dist/assets/geo-DUvVbLnx.js +0 -1
package/dist/cli/index.js CHANGED
@@ -782,6 +782,20 @@ function validateNodeDefinition(value) {
782
782
  );
783
783
  if ("step" in raw && (typeof raw.step !== "number" || !Number.isFinite(raw.step) || raw.step <= 0))
784
784
  add("invalid-step", "step must be greater than zero", `${path23}.step`);
785
+ if (section === "props" && "expression" in raw && (typeof raw.expression !== "string" || raw.expression.length === 0))
786
+ add(
787
+ "invalid-expression",
788
+ "expression must be a non-empty string",
789
+ `${path23}.expression`
790
+ );
791
+ for (const field of ["min", "max"]) {
792
+ if (field in raw && (typeof raw[field] !== "number" || !Number.isFinite(raw[field])))
793
+ add(
794
+ `invalid-${field}`,
795
+ `${field} must be a finite number`,
796
+ `${path23}.${field}`
797
+ );
798
+ }
785
799
  if (typeof raw.min === "number" && typeof raw.max === "number" && raw.min > raw.max)
786
800
  add("invalid-range", "min must not exceed max", path23);
787
801
  }
@@ -801,7 +815,7 @@ var init_validate = __esm({
801
815
  CONTROLS = /* @__PURE__ */ new Set(["number", "slider", "range", "int", "boolean", "text", "textarea", "select", "vector", "matrix", "color", "image", "asset"]);
802
816
  CAPABILITIES_BY_ENVIRONMENT = {
803
817
  portable: ["assets", "media"],
804
- browser: ["assets", "media", "webgl"],
818
+ browser: ["assets", "media", "gpu"],
805
819
  server: ["files", "assets", "media", "python", "shell"]
806
820
  };
807
821
  ALLOWED = CAPABILITIES_BY_ENVIRONMENT;
@@ -814,7 +828,7 @@ var init_validate = __esm({
814
828
  );
815
829
  OUTPUT_FIELDS = fields("kind type description");
816
830
  PROP_FIELDS = fields(
817
- "type default label description min max step accept control options"
831
+ "type default label description min max step accept control options expression"
818
832
  );
819
833
  }
820
834
  });
@@ -5750,6 +5764,17 @@ var Node = class _Node {
5750
5764
  this.portsUsedDuringSetup = /* @__PURE__ */ new Set();
5751
5765
  // Port index map for O(1) lookups (portId -> port)
5752
5766
  this._portMap = /* @__PURE__ */ new Map();
5767
+ // ============ Props System ============
5768
+ /**
5769
+ * Add a parameter to this node
5770
+ * Short alias for defineProp - use in setup()
5771
+ */
5772
+ /** Props whose declared default expression has already been applied — see
5773
+ * `bindParameterProp`, which must not reinstate one the author deleted. */
5774
+ this.defaultExpressionsApplied = /* @__PURE__ */ new Set();
5775
+ /** Props whose plain stored value explicitly overrides a declared default
5776
+ * expression. The marker survives even when that value equals the default. */
5777
+ this.plainPropOverrides = /* @__PURE__ */ new Set();
5753
5778
  this.id = id;
5754
5779
  this.type = type;
5755
5780
  this.graph = graph;
@@ -5884,11 +5909,6 @@ var Node = class _Node {
5884
5909
  targetPort.onChange?.(value);
5885
5910
  }
5886
5911
  }
5887
- // ============ Props System ============
5888
- /**
5889
- * Add a parameter to this node
5890
- * Short alias for defineProp - use in setup()
5891
- */
5892
5912
  addParm(name, config) {
5893
5913
  this.props[name] = config;
5894
5914
  this.propsDeclaredByCode.add(name);
@@ -5989,6 +6009,9 @@ var Node = class _Node {
5989
6009
  deleteExpression: () => {
5990
6010
  const { expression: _, expressionError: __, ...rest } = self.props[name];
5991
6011
  self.props[name] = rest;
6012
+ if (self.defaultExpressionsApplied.has(name)) {
6013
+ self.plainPropOverrides.add(name);
6014
+ }
5992
6015
  self.updateTimeDependent();
5993
6016
  self.markDirty();
5994
6017
  },
@@ -6068,6 +6091,12 @@ var Node = class _Node {
6068
6091
  ...channel ? { channel } : {}
6069
6092
  };
6070
6093
  }
6094
+ preservePlainProp(name) {
6095
+ this.plainPropOverrides.add(name);
6096
+ }
6097
+ preservesPlainProp(name) {
6098
+ return this.plainPropOverrides.has(name) && this.defaultExpressionsApplied.has(name);
6099
+ }
6071
6100
  /** Read a channel back off disk. Bad data yields no channel, never a throw. */
6072
6101
  restorePropChannel(name, data) {
6073
6102
  if (!this.props[name]) return;
@@ -6215,6 +6244,14 @@ var Node = class _Node {
6215
6244
  this.props[name] = prop;
6216
6245
  this.props = { ...this.props };
6217
6246
  }
6247
+ if (options.defaultExpression && !this.defaultExpressionsApplied.has(name)) {
6248
+ this.defaultExpressionsApplied.add(name);
6249
+ if (!this.plainPropOverrides.has(name) && !prop.expression) {
6250
+ prop.expression = options.defaultExpression;
6251
+ this.props = { ...this.props };
6252
+ this.updateTimeDependent();
6253
+ }
6254
+ }
6218
6255
  prop.fromParameter = name;
6219
6256
  prop.hidden = true;
6220
6257
  const control = paramControlType(parameter.dataType);
@@ -8103,6 +8140,7 @@ var silentProgress = { report: () => {
8103
8140
  function parameterOptions(definition) {
8104
8141
  return {
8105
8142
  ...definition.label ? { label: definition.label } : {},
8143
+ ...definition.expression ? { defaultExpression: definition.expression } : {},
8106
8144
  ...definition.min === void 0 ? {} : { min: definition.min },
8107
8145
  ...definition.max === void 0 ? {} : { max: definition.max },
8108
8146
  ...definition.step === void 0 ? {} : { step: definition.step },
@@ -8112,6 +8150,9 @@ function parameterOptions(definition) {
8112
8150
  promotable: false
8113
8151
  };
8114
8152
  }
8153
+ function asGpuHost(value) {
8154
+ return value && typeof value.ensure === "function" && typeof value.forNode === "function" ? value : null;
8155
+ }
8115
8156
  function attachDefinition(node, registration, capabilities) {
8116
8157
  const definition = registration.definition;
8117
8158
  const pendingTriggers = /* @__PURE__ */ new Map();
@@ -8186,12 +8227,18 @@ function attachDefinition(node, registration, capabilities) {
8186
8227
  if (!port) throw new Error(`Missing output ${registration.moduleId}.${name}`);
8187
8228
  return [name, output.kind === "trigger" ? { trigger: (payload) => port.trigger(payload) } : { set: (value) => port.setValue(value) }];
8188
8229
  }));
8230
+ let nodeCapabilities = capabilities;
8231
+ const gpuHost = (definition.capabilities ?? []).includes("gpu") ? asGpuHost(capabilities.gpu) : null;
8232
+ if (gpuHost) {
8233
+ await gpuHost.ensure();
8234
+ nodeCapabilities = { ...capabilities, gpu: gpuHost.forNode(node.id) };
8235
+ }
8189
8236
  const context = {
8190
8237
  nodeId: node.id,
8191
8238
  inputs: inputValues,
8192
8239
  outputs,
8193
8240
  props: Object.fromEntries(node.parameters.map((parameter) => [parameter.name, parameter.value])),
8194
- capabilities,
8241
+ capabilities: nodeCapabilities,
8195
8242
  signal: new AbortController().signal,
8196
8243
  progress: silentProgress
8197
8244
  };
@@ -8230,8 +8277,140 @@ var browserAssetCapability = {
8230
8277
  }
8231
8278
  };
8232
8279
 
8280
+ // src/nodes/definition/gpuCapability.ts
8281
+ var PAGE_SCOPE = "\0page";
8282
+ var MISSING_DEVICE = "The gpu capability was used before its device was ready \u2014 the host must await ensure() before a cook.";
8283
+ function createStudioGpuCapability() {
8284
+ const gpu = globalThis.navigator?.gpu;
8285
+ if (!gpu) return void 0;
8286
+ let device = null;
8287
+ let adapterInfo = null;
8288
+ let limits = {};
8289
+ let starting = null;
8290
+ let destroyed = false;
8291
+ const caches = /* @__PURE__ */ new Map();
8292
+ function dropCaches() {
8293
+ for (const entries of caches.values()) {
8294
+ for (const entry of entries.values()) entry.destroy?.(entry.value);
8295
+ }
8296
+ caches.clear();
8297
+ }
8298
+ async function start() {
8299
+ const adapter = await gpu.requestAdapter();
8300
+ if (!adapter) {
8301
+ throw new Error("No WebGPU adapter is available \u2014 the browser reports WebGPU but the system offers no GPU to render on.");
8302
+ }
8303
+ const next = await adapter.requestDevice();
8304
+ const info = adapter.info;
8305
+ device = next;
8306
+ adapterInfo = {
8307
+ vendor: info?.vendor ?? "",
8308
+ architecture: info?.architecture ?? "",
8309
+ device: info?.device ?? "",
8310
+ description: info?.description ?? ""
8311
+ };
8312
+ limits = readLimits(next.limits ?? adapter.limits);
8313
+ void next.lost.then((info2) => {
8314
+ if (destroyed || info2?.reason === "destroyed") return;
8315
+ device = null;
8316
+ starting = null;
8317
+ dropCaches();
8318
+ console.warn(`The GPU device was lost (${info2?.reason ?? "unknown"}) \u2014 ${info2?.message || "no message given"}. A fresh device will be requested on the next cook.`);
8319
+ });
8320
+ }
8321
+ const host = {
8322
+ get device() {
8323
+ if (!device) throw new Error(MISSING_DEVICE);
8324
+ return device;
8325
+ },
8326
+ get adapterInfo() {
8327
+ if (!adapterInfo) throw new Error(MISSING_DEVICE);
8328
+ return adapterInfo;
8329
+ },
8330
+ get limits() {
8331
+ return limits;
8332
+ },
8333
+ cache(key2, create, destroy) {
8334
+ return host.forNode(PAGE_SCOPE).cache(key2, create, destroy);
8335
+ },
8336
+ async ensure() {
8337
+ if (destroyed) throw new Error("The gpu capability has been disposed.");
8338
+ if (device) return;
8339
+ starting ??= start().catch((error) => {
8340
+ starting = null;
8341
+ throw error;
8342
+ });
8343
+ await starting;
8344
+ },
8345
+ forNode(nodeId) {
8346
+ return {
8347
+ get device() {
8348
+ if (!device) throw new Error(MISSING_DEVICE);
8349
+ return device;
8350
+ },
8351
+ get adapterInfo() {
8352
+ if (!adapterInfo) throw new Error(MISSING_DEVICE);
8353
+ return adapterInfo;
8354
+ },
8355
+ get limits() {
8356
+ return limits;
8357
+ },
8358
+ cache(key2, create, destroy) {
8359
+ if (!device) throw new Error(MISSING_DEVICE);
8360
+ let entries = caches.get(nodeId);
8361
+ if (!entries) {
8362
+ entries = /* @__PURE__ */ new Map();
8363
+ caches.set(nodeId, entries);
8364
+ }
8365
+ const existing = entries.get(key2);
8366
+ if (existing) return existing.value;
8367
+ const value = create(device);
8368
+ entries.set(key2, { value, destroy });
8369
+ return value;
8370
+ }
8371
+ };
8372
+ },
8373
+ async dispose() {
8374
+ destroyed = true;
8375
+ dropCaches();
8376
+ device?.destroy();
8377
+ device = null;
8378
+ starting = null;
8379
+ }
8380
+ };
8381
+ return host;
8382
+ }
8383
+ function readLimits(source) {
8384
+ if (!source || typeof source !== "object") return {};
8385
+ const out = {};
8386
+ for (const key2 of limitNames(source)) {
8387
+ const value = source[key2];
8388
+ if (typeof value === "number") out[key2] = value;
8389
+ }
8390
+ return out;
8391
+ }
8392
+ function limitNames(source) {
8393
+ const names = new Set(Object.keys(source));
8394
+ for (let cursor = Object.getPrototypeOf(source); cursor && cursor !== Object.prototype; cursor = Object.getPrototypeOf(cursor)) {
8395
+ for (const key2 of Object.getOwnPropertyNames(cursor)) if (key2 !== "constructor") names.add(key2);
8396
+ }
8397
+ return [...names];
8398
+ }
8399
+
8233
8400
  // src/nodes/definition/projectDefinition.ts
8234
- var studioCapabilities = { assets: browserAssetCapability };
8401
+ var studioCapabilities = {
8402
+ assets: browserAssetCapability,
8403
+ // Installed only where the browser has WebGPU, and absent rather than
8404
+ // stubbed otherwise. That is what makes the failure a sentence: the preflight
8405
+ // reports `runtime/missing-capability` and the adapter throws "requires the
8406
+ // Studio gpu capability", both naming what is missing. A stub that threw on
8407
+ // first use would name nothing.
8408
+ ...gpuCapabilityIfAvailable()
8409
+ };
8410
+ function gpuCapabilityIfAvailable() {
8411
+ const gpu = createStudioGpuCapability();
8412
+ return gpu ? { gpu } : {};
8413
+ }
8235
8414
  function declaresDefinitionV1(value) {
8236
8415
  return typeof value === "object" && value !== null && value.apiVersion === 1;
8237
8416
  }
@@ -9429,7 +9608,9 @@ var Graph = class _Graph {
9429
9608
  result.parent = node.parent.id;
9430
9609
  }
9431
9610
  const rawParameterValue = (p) => node.rawParameterValue(p.name);
9432
- const changedParameters = (node.parameters ?? []).filter((p) => p.promoted || JSON.stringify(rawParameterValue(p)) !== JSON.stringify(p.defaultValue));
9611
+ const changedParameters = (node.parameters ?? []).filter(
9612
+ (p) => p.promoted || node.preservesPlainProp(p.name) || JSON.stringify(rawParameterValue(p)) !== JSON.stringify(p.defaultValue)
9613
+ );
9433
9614
  const parameters = changedParameters.filter((p) => p.documentField !== "props").map((p) => p.promoted ? { name: p.name, value: rawParameterValue(p), promoted: true } : { name: p.name, value: rawParameterValue(p) });
9434
9615
  if (parameters.length > 0) {
9435
9616
  result.params = parameters;
@@ -9644,6 +9825,12 @@ var Graph = class _Graph {
9644
9825
  }
9645
9826
  if (expression && node.props[key2]) {
9646
9827
  node.props[key2].expression = expression;
9828
+ } else {
9829
+ node.preservePlainProp(key2);
9830
+ }
9831
+ if (!expression && node.props[key2]?.expression) {
9832
+ delete node.props[key2].expression;
9833
+ node.props[key2] = { ...node.props[key2] };
9647
9834
  }
9648
9835
  if (propData?.channel && node.props[key2]) {
9649
9836
  node.restorePropChannel(key2, propData.channel);
@@ -11411,6 +11598,9 @@ var PREFLIGHT_WARNING_CODES = /* @__PURE__ */ new Set([
11411
11598
  "runtime/preflight-environment",
11412
11599
  "runtime/stray-params"
11413
11600
  ]);
11601
+ var RUN_TOLERATED_CODES = /* @__PURE__ */ new Set([
11602
+ "runtime/stray-params"
11603
+ ]);
11414
11604
  var MutableAbortSignal = class {
11415
11605
  constructor(report) {
11416
11606
  this.report = report;
@@ -11559,6 +11749,46 @@ var Graph2 = class {
11559
11749
  this.frame = MIN_FRAME;
11560
11750
  this.fps = DEFAULT_FPS;
11561
11751
  this.byId = new Map(nodes.map((node) => [node.id, node]));
11752
+ const byInput = /* @__PURE__ */ new Map();
11753
+ const byNode = /* @__PURE__ */ new Map();
11754
+ const connectedInputs = /* @__PURE__ */ new Set();
11755
+ const connectedOutputs = /* @__PURE__ */ new Set();
11756
+ for (const connection of connections) {
11757
+ connectedInputs.add(
11758
+ `${connection.target.nodeId}\0${connection.target.inputName}`
11759
+ );
11760
+ connectedOutputs.add(
11761
+ `${connection.source.nodeId}\0${connection.source.outputName}`
11762
+ );
11763
+ if (connection.kind !== "data") continue;
11764
+ const inputKey = `${connection.target.nodeId}\0${connection.target.inputName}`;
11765
+ const inputConnections = byInput.get(inputKey) ?? [];
11766
+ inputConnections.push(connection);
11767
+ byInput.set(inputKey, inputConnections);
11768
+ const nodeConnections = byNode.get(connection.target.nodeId) ?? [];
11769
+ nodeConnections.push(connection);
11770
+ byNode.set(connection.target.nodeId, nodeConnections);
11771
+ }
11772
+ this.dataConnectionsByInput = new Map(
11773
+ [...byInput].map(([key2, inputConnections]) => [
11774
+ key2,
11775
+ Object.freeze(
11776
+ inputConnections.sort(
11777
+ (first, second) => (first.variadicIndex ?? first.order) - (second.variadicIndex ?? second.order)
11778
+ )
11779
+ )
11780
+ ])
11781
+ );
11782
+ this.dataConnectionsByNode = new Map(
11783
+ [...byNode].map(([key2, nodeConnections]) => [
11784
+ key2,
11785
+ Object.freeze(
11786
+ nodeConnections.sort((first, second) => first.order - second.order)
11787
+ )
11788
+ ])
11789
+ );
11790
+ this.connectedInputs = connectedInputs;
11791
+ this.connectedOutputs = connectedOutputs;
11562
11792
  this.graphInputs = graphBoundaries(nodes, "cascade.core.Input", "inputName");
11563
11793
  this.graphOutputs = graphBoundaries(nodes, "cascade.core.Output", "outputName");
11564
11794
  this.authoredAnnotations = Object.freeze(
@@ -11568,9 +11798,7 @@ var Graph2 = class {
11568
11798
  nodes,
11569
11799
  node: (id) => this.byId.get(id),
11570
11800
  inputNode: (node, index) => {
11571
- const feeding = this.connections.filter(
11572
- (connection) => connection.kind === "data" && connection.target.nodeId === node.id
11573
- ).sort((a, b) => a.order - b.order);
11801
+ const feeding = this.dataConnectionsByNode.get(node.id) ?? [];
11574
11802
  const source = feeding[index]?.source.nodeId;
11575
11803
  return (source ? this.byId.get(source) : void 0) ?? null;
11576
11804
  },
@@ -11614,9 +11842,7 @@ var Graph2 = class {
11614
11842
  {
11615
11843
  kind: definition.kind,
11616
11844
  type: definition.kind === "trigger" ? "trigger" : definition.type,
11617
- connected: this.connections.some(
11618
- (connection) => connection.target.nodeId === node.id && connection.target.inputName === name
11619
- ),
11845
+ connected: this.connectedInputs.has(`${node.id}\0${name}`),
11620
11846
  ...definition.kind === "data" ? { value: snapshot(node.inputs[name]) } : {}
11621
11847
  }
11622
11848
  ]
@@ -11629,9 +11855,7 @@ var Graph2 = class {
11629
11855
  {
11630
11856
  kind: definition.kind,
11631
11857
  type: definition.kind === "trigger" ? "trigger" : definition.type,
11632
- connected: this.connections.some(
11633
- (connection) => connection.source.nodeId === node.id && connection.source.outputName === name
11634
- ),
11858
+ connected: this.connectedOutputs.has(`${node.id}\0${name}`),
11635
11859
  ...definition.kind === "data" && node.outputs.has(name) ? { value: snapshot(node.outputs.get(name)) } : {}
11636
11860
  }
11637
11861
  ]
@@ -11981,11 +12205,7 @@ var Graph2 = class {
11981
12205
  const index = integerProp(node.props.inputIndex);
11982
12206
  return [name, this.dataInputValue(parent, `input_${index}`)];
11983
12207
  }
11984
- const connections = this.connections.filter(
11985
- (item) => item.kind === "data" && item.target.nodeId === node.id && item.target.inputName === name
11986
- ).sort(
11987
- (first, second) => (first.variadicIndex ?? first.order) - (second.variadicIndex ?? second.order)
11988
- );
12208
+ const connections = this.dataConnectionsByInput.get(`${node.id}\0${name}`) ?? [];
11989
12209
  if (definition.variadic) {
11990
12210
  const values = connections.map(
11991
12211
  (connection2) => this.requireNode(connection2.source.nodeId).outputs.get(
@@ -12215,7 +12435,7 @@ var Graph2 = class {
12215
12435
  }
12216
12436
  assertPreflight(nodes) {
12217
12437
  const [first] = this.preflightDiagnostics(nodes).filter(
12218
- (diagnostic2) => !PREFLIGHT_WARNING_CODES.has(diagnostic2.code)
12438
+ (diagnostic2) => !RUN_TOLERATED_CODES.has(diagnostic2.code)
12219
12439
  );
12220
12440
  if (first) throw new CascadeRuntimeError(first.code, [first]);
12221
12441
  }
@@ -12229,8 +12449,9 @@ var Graph2 = class {
12229
12449
  }
12230
12450
  runSelection(target) {
12231
12451
  if (target.kind === "all") {
12452
+ const gatedDataDescendants = this.gatedDataDescendants();
12232
12453
  const initial = this.nodes.filter(
12233
- (node) => !isGated(node) && !this.hasGatedDataAncestor(node.id)
12454
+ (node) => !isGated(node) && !gatedDataDescendants.has(node.id)
12234
12455
  );
12235
12456
  const ids = new Set(initial.map((node) => node.id));
12236
12457
  let changed = true;
@@ -12278,8 +12499,24 @@ var Graph2 = class {
12278
12499
  }
12279
12500
  return this.nodes.filter((node) => ids.has(node.id));
12280
12501
  }
12281
- hasGatedDataAncestor(nodeId) {
12282
- return this.dataClosure(nodeId).some(isGated);
12502
+ gatedDataDescendants() {
12503
+ const outgoing = /* @__PURE__ */ new Map();
12504
+ for (const dependency of dataDependencies(this.nodes, this.connections)) {
12505
+ const targets = outgoing.get(dependency.sourceId) ?? [];
12506
+ targets.push(dependency.targetId);
12507
+ outgoing.set(dependency.sourceId, targets);
12508
+ }
12509
+ const queue = this.nodes.filter(isGated).map((node) => node.id);
12510
+ const descendants = new Set(queue);
12511
+ let queueIndex = 0;
12512
+ while (queueIndex < queue.length) {
12513
+ for (const targetId of outgoing.get(queue[queueIndex++]) ?? []) {
12514
+ if (descendants.has(targetId)) continue;
12515
+ descendants.add(targetId);
12516
+ queue.push(targetId);
12517
+ }
12518
+ }
12519
+ return descendants;
12283
12520
  }
12284
12521
  triggerDataAncestors(nodeId) {
12285
12522
  const dependencies = this.dataClosure(nodeId).filter(
@@ -12455,7 +12692,17 @@ function materialize(authored, registration) {
12455
12692
  `${authored.id}.${name}`
12456
12693
  );
12457
12694
  const bindings = {};
12458
- for (const [name, saved] of Object.entries(authored.props ?? {})) {
12695
+ const storedProps = authored.props ?? {};
12696
+ for (const [name, definition] of Object.entries(
12697
+ registration.definition.props ?? {}
12698
+ )) {
12699
+ if (!definition.expression || name in storedProps) continue;
12700
+ bindings[name] = {
12701
+ value: props[name],
12702
+ expression: definition.expression
12703
+ };
12704
+ }
12705
+ for (const [name, saved] of Object.entries(storedProps)) {
12459
12706
  const definition = registration.definition.props?.[name];
12460
12707
  if (!definition) continue;
12461
12708
  const object = saved && typeof saved === "object" && "value" in saved ? saved : null;
@@ -12466,7 +12713,10 @@ function materialize(authored, registration) {
12466
12713
  `${authored.id}.${name}`
12467
12714
  );
12468
12715
  props[name] = value;
12469
- if (!object) continue;
12716
+ if (!object) {
12717
+ delete bindings[name];
12718
+ continue;
12719
+ }
12470
12720
  const channel = deserializeChannel(object.channel);
12471
12721
  const expression = typeof object.expression === "string" && object.expression ? object.expression : void 0;
12472
12722
  const binding = {
@@ -12727,6 +12977,7 @@ function validateTopology(nodes, connections) {
12727
12977
  topological(nodes, connections);
12728
12978
  }
12729
12979
  function topological(nodes, connections) {
12980
+ const byId = new Map(nodes.map((node) => [node.id, node]));
12730
12981
  const indegree = new Map(nodes.map((node) => [node.id, 0]));
12731
12982
  const outgoing = new Map(nodes.map((node) => [node.id, []]));
12732
12983
  for (const dependency of graphDependencies(nodes, connections)) {
@@ -12738,13 +12989,14 @@ function topological(nodes, connections) {
12738
12989
  }
12739
12990
  const queue = nodes.filter((node) => indegree.get(node.id) === 0);
12740
12991
  const result = [];
12741
- while (queue.length) {
12742
- const node = queue.shift();
12992
+ let queueIndex = 0;
12993
+ while (queueIndex < queue.length) {
12994
+ const node = queue[queueIndex++];
12743
12995
  result.push(node);
12744
12996
  for (const id of outgoing.get(node.id)) {
12745
12997
  indegree.set(id, indegree.get(id) - 1);
12746
12998
  if (indegree.get(id) === 0)
12747
- queue.push(nodes.find((candidate) => candidate.id === id));
12999
+ queue.push(byId.get(id));
12748
13000
  }
12749
13001
  }
12750
13002
  if (result.length !== nodes.length)
@@ -13163,6 +13415,15 @@ async function preflightAgainstRunHost(file, registrations, document2) {
13163
13415
  }
13164
13416
  if (!diagnostics.length) return;
13165
13417
  const { errors, warnings } = classifyPreflight(diagnostics);
13418
+ reportPreflightWarnings(host, warnings);
13419
+ if (!errors.length) return;
13420
+ throw new Error([
13421
+ `This graph cannot run on the ${host.environment} host that "cascade run" uses:`,
13422
+ ...errors.map(line),
13423
+ ...capabilityAdvice(host, errors)
13424
+ ].join("\n"));
13425
+ }
13426
+ function reportPreflightWarnings(host, warnings) {
13166
13427
  const elsewhere = warnings.filter((item) => item.code === "runtime/preflight-environment");
13167
13428
  const lossy = warnings.filter((item) => item.code === "runtime/stray-params");
13168
13429
  if (elsewhere.length) {
@@ -13179,12 +13440,6 @@ async function preflightAgainstRunHost(file, registrations, document2) {
13179
13440
  ` This graph will run, on the defaults, and look like it worked.`
13180
13441
  ].join("\n"));
13181
13442
  }
13182
- if (!errors.length) return;
13183
- throw new Error([
13184
- `This graph cannot run on the ${host.environment} host that "cascade run" uses:`,
13185
- ...errors.map(line),
13186
- ...capabilityAdvice(host, errors)
13187
- ].join("\n"));
13188
13443
  }
13189
13444
  function line(item) {
13190
13445
  return ` ${item.code}${item.path ? ` [${item.path}]` : ""}: ${item.message}`;
@@ -13242,21 +13497,31 @@ async function runDeterministicProjectGraph(file, document2, entryNode) {
13242
13497
  if (deterministicCount !== nodes.length) {
13243
13498
  throw new Error("Mixed deterministic and dynamic graphs are not executable until the bounded legacy adapter is implemented");
13244
13499
  }
13500
+ const host = createProjectHost(file, prepared.registrations);
13245
13501
  const runtime = createRuntime({
13246
- host: createProjectHost(file, prepared.registrations),
13502
+ host,
13247
13503
  nodes: prepared.registrations
13248
13504
  });
13249
- const graph = await runtime.load(document2);
13250
- const result = await graph.run(entryNode ? { target: { kind: "node", nodeId: entryNode } } : {});
13251
- await graph.dispose();
13252
- await runtime.dispose();
13253
- if (result.status !== "completed") {
13254
- throw new Error(result.diagnostics.map((item) => item.message).join("\n") || `Graph ${result.status}`);
13505
+ let graph;
13506
+ try {
13507
+ graph = await runtime.load(document2);
13508
+ reportPreflightWarnings(
13509
+ host,
13510
+ classifyPreflight(graph.preflight()).warnings.filter((item) => item.code === "runtime/stray-params")
13511
+ );
13512
+ const result = await graph.run(entryNode ? { target: { kind: "node", nodeId: entryNode } } : {});
13513
+ if (result.status !== "completed") {
13514
+ throw new Error(result.diagnostics.map((item) => item.message).join("\n") || `Graph ${result.status}`);
13515
+ }
13516
+ return true;
13517
+ } finally {
13518
+ await graph?.dispose();
13519
+ await runtime.dispose();
13255
13520
  }
13256
- return true;
13257
13521
  }
13258
13522
  async function renderDeterministicProjectFrames(file, document2, options) {
13259
13523
  const prepared = await prepare(file, document2, true);
13524
+ reportUnresolved(prepared);
13260
13525
  const nodes = Array.isArray(document2?.nodes) ? document2.nodes : [];
13261
13526
  const deterministicCount = nodes.filter((node) => prepared.deterministic.has(moduleId(node))).length;
13262
13527
  if (!nodes.length || deterministicCount === 0) return null;
@@ -13264,11 +13529,13 @@ async function renderDeterministicProjectFrames(file, document2, options) {
13264
13529
  throw new Error("Mixed deterministic and dynamic graphs are not executable until the bounded legacy adapter is implemented");
13265
13530
  }
13266
13531
  const project = new ProjectRoot(path9.dirname(path9.resolve(file)));
13532
+ const host = createProjectHost(file, prepared.registrations);
13267
13533
  const runtime = createRuntime({
13268
- host: createProjectHost(file, prepared.registrations),
13534
+ host,
13269
13535
  nodes: prepared.registrations
13270
13536
  });
13271
13537
  const graph = await runtime.load(document2);
13538
+ reportPreflightWarnings(host, classifyPreflight(graph.preflight()).warnings);
13272
13539
  const outDirectory = project.resolve(options.out);
13273
13540
  await fs7.mkdir(outDirectory, { recursive: true });
13274
13541
  const width = sequenceWidth(options.end);
@@ -13328,6 +13595,7 @@ function imageOutputs(graph, entryNode) {
13328
13595
  }
13329
13596
  async function inspectProjectGraph(file, document2) {
13330
13597
  const prepared = await prepare(file, document2, false);
13598
+ reportUnresolved(prepared);
13331
13599
  const registrations = new Map(
13332
13600
  [...builtinNodeRegistrations, ...prepared.registrations].map((item) => [item.moduleId, item])
13333
13601
  );
@@ -13422,7 +13690,7 @@ function unresolvedModule(id, node, tried, root) {
13422
13690
  phase: "prepare",
13423
13691
  code: "project/module-not-found",
13424
13692
  path: node.id,
13425
- message: `${id} has no module file \u2014 ${where}. Loading the ports the document saved instead, which cannot run: a typo and an unconverted legacy node look identical from here`
13693
+ message: `${id} has no module file \u2014 ${where}. Only known legacy modules can use saved-port compatibility, for inspection only; unknown and explicit project modules are rejected`
13426
13694
  };
13427
13695
  }
13428
13696
  async function compileExecute(projectRoot, moduleFile) {
@@ -0,0 +1,76 @@
1
+ /**
2
+ * `cascade/gpu` — WebGPU's flag namespaces as plain values.
3
+ *
4
+ * Small, and it was load-bearing. TypeScript's DOM library declares the WebGPU
5
+ * *interfaces* but not the constant objects, so the only way to reach
6
+ * `GPUTextureUsage.RENDER_ATTACHMENT` was off `globalThis` — which
7
+ * `architecture/ambient-state` correctly forbids inside a node module, because
8
+ * an ambient read makes a node's inputs invisible to the static reader that
9
+ * lets `cascade check` say anything without cooking.
10
+ *
11
+ * So a sketch had to choose between a rule and a working shader, and it chose
12
+ * a project library file to hide the read in. Both stay honest now: the rule
13
+ * keeps its teeth and a node imports these instead. That is the whole reason
14
+ * this file exists, and it is why it had to land with the capability rather
15
+ * than after it — a rule whose only workaround is removed at the same moment
16
+ * is a rule; one enforced a release earlier is a nuisance.
17
+ *
18
+ * The values are fixed by the WebGPU specification, not by an implementation,
19
+ * which is what makes writing them down safe rather than a guess. They are
20
+ * checked against a real browser's own globals in `tests/gpu-usage-flags.test.ts`
21
+ * — the numbers below were verified against Chrome rather than recalled.
22
+ */
23
+
24
+ /** `GPUBufferUsage`. */
25
+ export const BufferUsage = {
26
+ MAP_READ: 0x0001,
27
+ MAP_WRITE: 0x0002,
28
+ COPY_SRC: 0x0004,
29
+ COPY_DST: 0x0008,
30
+ INDEX: 0x0010,
31
+ VERTEX: 0x0020,
32
+ UNIFORM: 0x0040,
33
+ STORAGE: 0x0080,
34
+ INDIRECT: 0x0100,
35
+ QUERY_RESOLVE: 0x0200,
36
+ } as const;
37
+
38
+ /** `GPUTextureUsage`. */
39
+ export const TextureUsage = {
40
+ COPY_SRC: 0x01,
41
+ COPY_DST: 0x02,
42
+ TEXTURE_BINDING: 0x04,
43
+ STORAGE_BINDING: 0x08,
44
+ RENDER_ATTACHMENT: 0x10,
45
+ /**
46
+ * Chrome 147 has this and it is newer than the rest — verified present
47
+ * 2026-09-08 alongside the others, but do not assume every implementation
48
+ * carries it. Included rather than omitted deliberately: leaving it out
49
+ * would send a node that wants it back to `globalThis`, which is the exact
50
+ * ambient read this file exists to remove, so an honest constant with a
51
+ * caveat beats an omission that forces a rule violation.
52
+ */
53
+ TRANSIENT_ATTACHMENT: 0x20,
54
+ } as const;
55
+
56
+ /** `GPUShaderStage`, for a bind group layout entry's `visibility`. */
57
+ export const ShaderStage = {
58
+ VERTEX: 0x1,
59
+ FRAGMENT: 0x2,
60
+ COMPUTE: 0x4,
61
+ } as const;
62
+
63
+ /** `GPUMapMode`, for `buffer.mapAsync`. */
64
+ export const MapMode = {
65
+ READ: 0x1,
66
+ WRITE: 0x2,
67
+ } as const;
68
+
69
+ /** `GPUColorWrite`, for a colour target's `writeMask`. */
70
+ export const ColorWrite = {
71
+ RED: 0x1,
72
+ GREEN: 0x2,
73
+ BLUE: 0x4,
74
+ ALPHA: 0x8,
75
+ ALL: 0xf,
76
+ } as const;