@field/cascade 0.7.1 → 0.8.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 (53) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/dist/assets/{CodeEditor-D-N0C2jT.js → CodeEditor-BEk-kIBd.js} +29 -29
  3. package/dist/assets/CodePanel-CeSdjXkg.js +2 -0
  4. package/dist/assets/CustomNodeDialog-BQbEhIwL.js +1 -0
  5. package/dist/assets/{ExportDialog-BJu2Bpvv.js → ExportDialog-CjTbbTZJ.js} +2 -2
  6. package/dist/assets/OpenGraphDialog-DCwH9itM.js +1 -0
  7. package/dist/assets/ProjectSettings-Dg7M5xt9.js +2 -0
  8. package/dist/assets/SettingsDialog-e7GgsjB9.js +1 -0
  9. package/dist/assets/VersionHistoryDialog-D6VBzSQ0.js +3 -0
  10. package/dist/assets/{core-DlkL34JF.js → core-BInLG4gw.js} +1 -1
  11. package/dist/assets/{cssMode-DaSSFZYi.js → cssMode-DfR2qmYG.js} +1 -1
  12. package/dist/assets/definitions-DZpOjWKR.js +1 -0
  13. package/dist/assets/{editor.api-CHUMRaMm.js → editor.api-Ck8_vgQV.js} +1 -1
  14. package/dist/assets/{freemarker2-DQ604q2A.js → freemarker2-fVn10Wn3.js} +1 -1
  15. package/dist/assets/geo-D2VPjAdt.js +1 -0
  16. package/dist/assets/{handlebars-09pcWz8x.js → handlebars-BCSchLT2.js} +1 -1
  17. package/dist/assets/{html-DtIKrZiD.js → html-CVpSv_k9.js} +1 -1
  18. package/dist/assets/{htmlMode-DgJ_4m1_.js → htmlMode-Da4ESTv0.js} +1 -1
  19. package/dist/assets/{image-CtUE4_I-.js → image-BLn--oSF.js} +1 -1
  20. package/dist/assets/index-CMiBdEuT.js +212 -0
  21. package/dist/assets/index-DMIuml9a.css +1 -0
  22. package/dist/assets/index-client-BI2H82FJ.js +1 -0
  23. package/dist/assets/{javascript-kbIBYYGo.js → javascript-BpX0Gvcf.js} +1 -1
  24. package/dist/assets/{jsonMode-DblCG64t.js → jsonMode-CsLqaou7.js} +1 -1
  25. package/dist/assets/{legacy-CAj2_7QE.js → legacy-ZA7textG.js} +1 -1
  26. package/dist/assets/{liquid-D-qdTGhQ.js → liquid-efUz51Wf.js} +1 -1
  27. package/dist/assets/{lspLanguageFeatures-Bc6azcFQ.js → lspLanguageFeatures-DCi5-K0n.js} +1 -1
  28. package/dist/assets/math-B6hpxPJO.js +1 -0
  29. package/dist/assets/{mdx-TXg4xsyD.js → mdx-DP_sjGVy.js} +1 -1
  30. package/dist/assets/pop-CYUIjT6x.js +1 -0
  31. package/dist/assets/{python-C4SGyXJW.js → python-aAuP3LUi.js} +1 -1
  32. package/dist/assets/{razor-BZ-lEgY9.js → razor-7M6sMmC5.js} +1 -1
  33. package/dist/assets/{tsMode-BoaO1D7x.js → tsMode-Bf4CnoYK.js} +1 -1
  34. package/dist/assets/{typescript-D87dYk9k.js → typescript-ddhYBGyT.js} +1 -1
  35. package/dist/assets/{workers-ZrST7klJ.js → workers-X5la8DDT.js} +1 -1
  36. package/dist/assets/{xml-DT6r4F7G.js → xml-BzbXa3Ps.js} +1 -1
  37. package/dist/assets/{yaml-DxmQ8XYb.js → yaml-CXs-VzxL.js} +1 -1
  38. package/dist/cli/index.js +257 -229
  39. package/dist/index.html +6 -4
  40. package/package.json +1 -1
  41. package/dist/assets/CodePanel-EN411mp5.js +0 -2
  42. package/dist/assets/CustomNodeDialog-6z2n27VV.js +0 -1
  43. package/dist/assets/OpenGraphDialog-fMZjzIR3.js +0 -1
  44. package/dist/assets/ProjectSettings-BQ8PGNqo.js +0 -2
  45. package/dist/assets/SettingsDialog-IbYLm8Bx.js +0 -1
  46. package/dist/assets/VersionHistoryDialog-Bgh-q5Jg.js +0 -3
  47. package/dist/assets/definitions-CQBdsJYJ.js +0 -1
  48. package/dist/assets/geo-DcmMAcqM.js +0 -1
  49. package/dist/assets/index-CnylT98t.js +0 -212
  50. package/dist/assets/index-client-BoJgsN9M.js +0 -1
  51. package/dist/assets/index-ilgbOu6G.css +0 -1
  52. package/dist/assets/math-CY11tkoN.js +0 -1
  53. package/dist/assets/pop-Dr5-ARqQ.js +0 -1
package/dist/cli/index.js CHANGED
@@ -212,9 +212,9 @@ function levelCount(geometry, level) {
212
212
  if (level === "primitive") return geometry.primitiveCount;
213
213
  return 1;
214
214
  }
215
- function checkName(name, path24) {
215
+ function checkName(name, path25) {
216
216
  if (!NAME.test(name) || RESERVED_NAMES.has(name))
217
- fail("invalid-name", `${path24}.${name} is not a valid attribute name`);
217
+ fail("invalid-name", `${path25}.${name} is not a valid attribute name`);
218
218
  }
219
219
  function checkAttributeSet(set, count, level) {
220
220
  for (const [name, attribute] of Object.entries(set)) {
@@ -826,8 +826,8 @@ var init_values = __esm({
826
826
  });
827
827
 
828
828
  // packages/contracts/src/validate.ts
829
- function diagnostic(code, message2, path24) {
830
- return { phase: "definition", code: `definition/${code}`, message: message2, path: path24 };
829
+ function diagnostic(code, message2, path25) {
830
+ return { phase: "definition", code: `definition/${code}`, message: message2, path: path25 };
831
831
  }
832
832
  function isRecord2(value) {
833
833
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -842,12 +842,12 @@ function finiteJson(value) {
842
842
  if (Array.isArray(value)) return value.every(finiteJson);
843
843
  return isRecord2(value) && Object.values(value).every(finiteJson);
844
844
  }
845
- function unknownFields(value, allowed, path24) {
845
+ function unknownFields(value, allowed, path25) {
846
846
  return Object.keys(value).filter((key2) => !allowed.has(key2)).map(
847
847
  (key2) => diagnostic(
848
848
  "unknown-field",
849
- `${path24}.${key2} is not allowed`,
850
- `${path24}.${key2}`
849
+ `${path25}.${key2} is not allowed`,
850
+ `${path25}.${key2}`
851
851
  )
852
852
  );
853
853
  }
@@ -879,7 +879,7 @@ function validateNodeDefinition(value) {
879
879
  if (!isRecord2(value))
880
880
  return [diagnostic("not-object", "Definition must be an object")];
881
881
  const result = unknownFields(value, TOP_FIELDS, "definition");
882
- const add = (code, message2, path24) => result.push(diagnostic(code, message2, path24));
882
+ const add = (code, message2, path25) => result.push(diagnostic(code, message2, path25));
883
883
  const environment = value.runsOn;
884
884
  if (value.apiVersion !== 1)
885
885
  add("api-version", "apiVersion must be 1", "apiVersion");
@@ -896,15 +896,15 @@ function validateNodeDefinition(value) {
896
896
  );
897
897
  const seen = /* @__PURE__ */ new Set();
898
898
  for (const [index, capability] of (Array.isArray(value.capabilities) ? value.capabilities : []).entries()) {
899
- const path24 = `capabilities.${index}`;
899
+ const path25 = `capabilities.${index}`;
900
900
  if (!seen.has(capability) && !allowed.includes(capability))
901
901
  add(
902
902
  "capability-not-allowed",
903
903
  `${String(capability)} is not available on ${String(environment)}`,
904
- path24
904
+ path25
905
905
  );
906
906
  if (seen.has(capability))
907
- add("duplicate-capability", `${String(capability)} is duplicated`, path24);
907
+ add("duplicate-capability", `${String(capability)} is duplicated`, path25);
908
908
  seen.add(capability);
909
909
  }
910
910
  const inward = /* @__PURE__ */ new Map();
@@ -917,31 +917,31 @@ function validateNodeDefinition(value) {
917
917
  continue;
918
918
  }
919
919
  for (const [name, raw] of Object.entries(entries2)) {
920
- const path24 = `${section}.${name}`;
920
+ const path25 = `${section}.${name}`;
921
921
  if (!IDENTIFIER.test(name))
922
- add("invalid-name", `${name} is not a valid identifier`, path24);
923
- if (RESERVED.has(name)) add("reserved-name", `${name} is reserved`, path24);
922
+ add("invalid-name", `${name} is not a valid identifier`, path25);
923
+ if (RESERVED.has(name)) add("reserved-name", `${name} is reserved`, path25);
924
924
  const names = section === "outputs" ? outward : inward;
925
925
  const previous = names.get(name);
926
926
  if (previous)
927
927
  add(
928
928
  "name-collision",
929
929
  `${name} is declared in ${previous} and ${section}`,
930
- path24
930
+ path25
931
931
  );
932
932
  else names.set(name, section);
933
933
  if (!isRecord2(raw)) {
934
- add("invalid-entry", `${path24} must be an object`, path24);
934
+ add("invalid-entry", `${path25} must be an object`, path25);
935
935
  continue;
936
936
  }
937
937
  const fields2 = section === "props" ? PROP_FIELDS : section === "outputs" ? OUTPUT_FIELDS : DATA_FIELDS;
938
938
  if (section !== "props" && raw.kind !== "data" && raw.kind !== "trigger") {
939
939
  add(
940
940
  "invalid-kind",
941
- `${path24}.kind must be data or trigger`,
942
- `${path24}.kind`
941
+ `${path25}.kind must be data or trigger`,
942
+ `${path25}.kind`
943
943
  );
944
- result.push(...unknownFields(raw, fields2, path24));
944
+ result.push(...unknownFields(raw, fields2, path25));
945
945
  continue;
946
946
  }
947
947
  if (section !== "props" && raw.kind === "trigger") {
@@ -951,22 +951,22 @@ function validateNodeDefinition(value) {
951
951
  add(
952
952
  "trigger-extra",
953
953
  "Trigger ports allow only kind and description",
954
- path24
954
+ path25
955
955
  );
956
956
  continue;
957
957
  }
958
- result.push(...unknownFields(raw, fields2, path24));
958
+ result.push(...unknownFields(raw, fields2, path25));
959
959
  if ("control" in raw && (typeof raw.control !== "string" || !CONTROLS.has(raw.control)))
960
960
  add(
961
961
  "invalid-control",
962
962
  `${String(raw.control)} is not a Cascade control`,
963
- `${path24}.control`
963
+ `${path25}.control`
964
964
  );
965
965
  if ("options" in raw && raw.control !== "select")
966
966
  add(
967
967
  "options-without-select",
968
968
  "options require the select control",
969
- `${path24}.options`
969
+ `${path25}.options`
970
970
  );
971
971
  if (Array.isArray(raw.options))
972
972
  for (const [index, option] of raw.options.entries()) {
@@ -976,19 +976,19 @@ function validateNodeDefinition(value) {
976
976
  add(
977
977
  "invalid-option",
978
978
  "a labelled option needs a value",
979
- `${path24}.options[${index}]`
979
+ `${path25}.options[${index}]`
980
980
  );
981
981
  if (typeof entry.label !== "string" || !entry.label)
982
982
  add(
983
983
  "invalid-option",
984
984
  "a labelled option needs a non-empty label",
985
- `${path24}.options[${index}].label`
985
+ `${path25}.options[${index}].label`
986
986
  );
987
987
  if ("disabled" in entry && typeof entry.disabled !== "boolean")
988
988
  add(
989
989
  "invalid-option",
990
990
  "disabled must be a boolean",
991
- `${path24}.options[${index}].disabled`
991
+ `${path25}.options[${index}].disabled`
992
992
  );
993
993
  }
994
994
  const type = raw.type;
@@ -996,56 +996,56 @@ function validateNodeDefinition(value) {
996
996
  add(
997
997
  "invalid-type",
998
998
  `${String(type)} is not a Cascade type`,
999
- `${path24}.type`
999
+ `${path25}.type`
1000
1000
  );
1001
1001
  continue;
1002
1002
  }
1003
1003
  if (type === "texture" && environment !== "browser")
1004
- add("texture-not-portable", "texture is browser-only", `${path24}.type`);
1004
+ add("texture-not-portable", "texture is browser-only", `${path25}.type`);
1005
1005
  if (type === "texture" && "default" in raw)
1006
1006
  add(
1007
1007
  "texture-default",
1008
1008
  "texture cannot have a default",
1009
- `${path24}.default`
1009
+ `${path25}.default`
1010
1010
  );
1011
1011
  if (section === "props" && !("default" in raw))
1012
- add("missing-default", "Props require defaults", `${path24}.default`);
1012
+ add("missing-default", "Props require defaults", `${path25}.default`);
1013
1013
  if ("default" in raw && !finiteJson(raw.default))
1014
1014
  add(
1015
1015
  "invalid-default",
1016
1016
  "Default must be finite JSON",
1017
- `${path24}.default`
1017
+ `${path25}.default`
1018
1018
  );
1019
1019
  else if ("default" in raw && !defaultMatches(type, raw.default))
1020
1020
  add(
1021
1021
  "default-type",
1022
1022
  `Default does not match ${type}`,
1023
- `${path24}.default`
1023
+ `${path25}.default`
1024
1024
  );
1025
1025
  if (type === "int" && "default" in raw && (!Number.isFinite(raw.default) || !Number.isInteger(raw.default)))
1026
1026
  add(
1027
1027
  "non-integer",
1028
1028
  "Integer defaults must be finite integers",
1029
- `${path24}.default`
1029
+ `${path25}.default`
1030
1030
  );
1031
1031
  if ("step" in raw && (typeof raw.step !== "number" || !Number.isFinite(raw.step) || raw.step <= 0))
1032
- add("invalid-step", "step must be greater than zero", `${path24}.step`);
1032
+ add("invalid-step", "step must be greater than zero", `${path25}.step`);
1033
1033
  if (section === "props" && "expression" in raw && (typeof raw.expression !== "string" || raw.expression.length === 0))
1034
1034
  add(
1035
1035
  "invalid-expression",
1036
1036
  "expression must be a non-empty string",
1037
- `${path24}.expression`
1037
+ `${path25}.expression`
1038
1038
  );
1039
1039
  for (const field of ["min", "max"]) {
1040
1040
  if (field in raw && (typeof raw[field] !== "number" || !Number.isFinite(raw[field])))
1041
1041
  add(
1042
1042
  `invalid-${field}`,
1043
1043
  `${field} must be a finite number`,
1044
- `${path24}.${field}`
1044
+ `${path25}.${field}`
1045
1045
  );
1046
1046
  }
1047
1047
  if (typeof raw.min === "number" && typeof raw.max === "number" && raw.min > raw.max)
1048
- add("invalid-range", "min must not exceed max", path24);
1048
+ add("invalid-range", "min must not exceed max", path25);
1049
1049
  }
1050
1050
  }
1051
1051
  return result;
@@ -1741,10 +1741,10 @@ var init_evaluator = __esm({
1741
1741
  fframe: this.timeState.frameFraction,
1742
1742
  fps: this.timeState.fps,
1743
1743
  self: node,
1744
- ch: (path24) => this.ch(node, path24),
1745
- chs: (path24) => this.chs(node, path24),
1746
- chv: (path24) => this.chv(node, path24),
1747
- opexist: (path24) => this.scope?.nodeExists(node, path24) ? 1 : 0,
1744
+ ch: (path25) => this.ch(node, path25),
1745
+ chs: (path25) => this.chs(node, path25),
1746
+ chv: (path25) => this.chv(node, path25),
1747
+ opexist: (path25) => this.scope?.nodeExists(node, path25) ? 1 : 0,
1748
1748
  opinput: (index) => this.scope?.inputNode(node, index) ?? null,
1749
1749
  fit,
1750
1750
  fit01,
@@ -1761,8 +1761,8 @@ var init_evaluator = __esm({
1761
1761
  };
1762
1762
  }
1763
1763
  // ============ Channel functions ============
1764
- ch(node, path24) {
1765
- const resolved = this.resolveParam(node, path24);
1764
+ ch(node, path25) {
1765
+ const resolved = this.resolveParam(node, path25);
1766
1766
  if (!resolved) return 0;
1767
1767
  const value = resolved.value;
1768
1768
  if (typeof value === "number") return value;
@@ -1770,13 +1770,13 @@ var init_evaluator = __esm({
1770
1770
  if (typeof value === "boolean") return value ? 1 : 0;
1771
1771
  return 0;
1772
1772
  }
1773
- chs(node, path24) {
1774
- const resolved = this.resolveParam(node, path24);
1773
+ chs(node, path25) {
1774
+ const resolved = this.resolveParam(node, path25);
1775
1775
  if (!resolved) return "";
1776
1776
  return String(resolved.value ?? "");
1777
1777
  }
1778
- chv(node, path24) {
1779
- const resolved = this.resolveParam(node, path24);
1778
+ chv(node, path25) {
1779
+ const resolved = this.resolveParam(node, path25);
1780
1780
  if (!resolved) return [];
1781
1781
  const value = resolved.value;
1782
1782
  if (Array.isArray(value)) {
@@ -1785,12 +1785,12 @@ var init_evaluator = __esm({
1785
1785
  if (typeof value === "number") return [value];
1786
1786
  return [];
1787
1787
  }
1788
- resolveParam(node, path24) {
1789
- return this.scope?.resolveParam(node, path24) ?? null;
1788
+ resolveParam(node, path25) {
1789
+ return this.scope?.resolveParam(node, path25) ?? null;
1790
1790
  }
1791
1791
  /** True if a referenced path resolves from this node. */
1792
- validatePath(fromNode, path24) {
1793
- return this.resolveParam(fromNode, path24) !== null;
1792
+ validatePath(fromNode, path25) {
1793
+ return this.resolveParam(fromNode, path25) !== null;
1794
1794
  }
1795
1795
  /** Parameter paths referenced through ch/chs/chv. */
1796
1796
  extractDependencies(expression) {
@@ -1829,14 +1829,14 @@ function geometryError(code, message2) {
1829
1829
  function attributeSet(geometry, level) {
1830
1830
  return geometry[level];
1831
1831
  }
1832
- function numericAttribute(attribute, path24, sizes) {
1832
+ function numericAttribute(attribute, path25, sizes) {
1833
1833
  if (attribute === void 0) return void 0;
1834
1834
  if (isStringAttribute(attribute))
1835
- geometryError("attribute-storage", `${path24} must be numeric`);
1835
+ geometryError("attribute-storage", `${path25} must be numeric`);
1836
1836
  if (sizes !== void 0 && !sizes.includes(attribute.size))
1837
1837
  geometryError(
1838
1838
  "attribute-size",
1839
- `${path24} has size ${attribute.size}; expected ${sizes.join(" or ")}`
1839
+ `${path25} has size ${attribute.size}; expected ${sizes.join(" or ")}`
1840
1840
  );
1841
1841
  return attribute;
1842
1842
  }
@@ -2647,7 +2647,7 @@ function geometryToSvg(geometry, options2 = {}) {
2647
2647
  if (mask2[primitive] === 1 && assigned[primitive] === -1)
2648
2648
  assigned[primitive] = index;
2649
2649
  }
2650
- const path24 = (primitive) => {
2650
+ const path25 = (primitive) => {
2651
2651
  const attributes = [`d="${pathData(geometry, primitive, number)}"`];
2652
2652
  const paint = paintOf(colour, primitive, "primitive");
2653
2653
  if (paint !== void 0) {
@@ -2666,13 +2666,13 @@ function geometryToSvg(geometry, options2 = {}) {
2666
2666
  const body = [];
2667
2667
  if (names.length === 0) {
2668
2668
  for (let primitive = 0; primitive < geometry.primitiveCount; primitive += 1)
2669
- body.push(indent(path24(primitive), 2));
2669
+ body.push(indent(path25(primitive), 2));
2670
2670
  } else {
2671
2671
  for (let index = 0; index < names.length; index += 1) {
2672
2672
  const members = [];
2673
2673
  for (let primitive = 0; primitive < geometry.primitiveCount; primitive += 1)
2674
2674
  if (assigned[primitive] === index)
2675
- members.push(indent(path24(primitive), 3));
2675
+ members.push(indent(path25(primitive), 3));
2676
2676
  body.push(
2677
2677
  indent(`<g id="${escapeXml(names[index])}">`, 2),
2678
2678
  ...members,
@@ -2681,7 +2681,7 @@ function geometryToSvg(geometry, options2 = {}) {
2681
2681
  }
2682
2682
  const rest = [];
2683
2683
  for (let primitive = 0; primitive < geometry.primitiveCount; primitive += 1)
2684
- if (assigned[primitive] === -1) rest.push(indent(path24(primitive), 3));
2684
+ if (assigned[primitive] === -1) rest.push(indent(path25(primitive), 3));
2685
2685
  if (rest.length > 0)
2686
2686
  body.push(
2687
2687
  indent(`<g id="${UNGROUPED}">`, 2),
@@ -2788,11 +2788,11 @@ function validateOptions(options2) {
2788
2788
  if (!Number.isSafeInteger(precision) || precision < 0 || precision > 15)
2789
2789
  geometryError("svg-precision", "precision must be an integer from 0 to 15");
2790
2790
  }
2791
- function validateRange(attribute, path24, accepts, expectation) {
2791
+ function validateRange(attribute, path25, accepts, expectation) {
2792
2792
  if (attribute === void 0) return;
2793
2793
  for (const value of attribute.data)
2794
2794
  if (!accepts(value))
2795
- geometryError("attribute-value", `${path24} values must be ${expectation}`);
2795
+ geometryError("attribute-value", `${path25} values must be ${expectation}`);
2796
2796
  }
2797
2797
  function indent(line2, depth) {
2798
2798
  return `${" ".repeat(depth)}${line2}`;
@@ -3081,8 +3081,8 @@ var init_resolve = __esm({
3081
3081
  });
3082
3082
 
3083
3083
  // packages/runtime/src/params/animator.ts
3084
- function split(path24) {
3085
- return String(path24 ?? "").split("/").filter((segment) => segment.length > 0 && segment !== ".");
3084
+ function split(path25) {
3085
+ return String(path25 ?? "").split("/").filter((segment) => segment.length > 0 && segment !== ".");
3086
3086
  }
3087
3087
  var PropAnimator;
3088
3088
  var init_animator = __esm({
@@ -3150,8 +3150,8 @@ var init_animator = __esm({
3150
3150
  */
3151
3151
  scope() {
3152
3152
  return {
3153
- resolveParam: (from, path24) => {
3154
- const segments = split(path24);
3153
+ resolveParam: (from, path25) => {
3154
+ const segments = split(path25);
3155
3155
  if (!segments.length) return null;
3156
3156
  const name = segments[segments.length - 1];
3157
3157
  const owner = this.owner(from, segments.slice(0, -1));
@@ -3161,8 +3161,8 @@ var init_animator = __esm({
3161
3161
  if (name in owner.inputs) return { value: owner.inputs[name] };
3162
3162
  return null;
3163
3163
  },
3164
- nodeExists: (from, path24) => {
3165
- const segments = split(path24);
3164
+ nodeExists: (from, path25) => {
3165
+ const segments = split(path25);
3166
3166
  if (!segments.length) return false;
3167
3167
  const last = segments[segments.length - 1];
3168
3168
  if (last === "..") return true;
@@ -5479,12 +5479,12 @@ function buildTrails(history, current, options2 = {}) {
5479
5479
  for (const frame of frames) {
5480
5480
  for (let index = 0; index < frame.id.length; index += 1) {
5481
5481
  const id = frame.id[index];
5482
- const path24 = paths.get(id) ?? [];
5483
- path24.push(
5482
+ const path25 = paths.get(id) ?? [];
5483
+ path25.push(
5484
5484
  frame.position[index * frame.size],
5485
5485
  frame.position[index * frame.size + 1]
5486
5486
  );
5487
- paths.set(id, path24);
5487
+ paths.set(id, path25);
5488
5488
  }
5489
5489
  }
5490
5490
  if (result === "velocity") {
@@ -5492,11 +5492,11 @@ function buildTrails(history, current, options2 = {}) {
5492
5492
  }
5493
5493
  const builder = new GeometryBuilder({ positionSize: 2 });
5494
5494
  const trailIds = [];
5495
- for (const [id, path24] of paths) {
5496
- if (path24.length < 4) continue;
5495
+ for (const [id, path25] of paths) {
5496
+ if (path25.length < 4) continue;
5497
5497
  const points = [];
5498
- for (let at = 0; at < path24.length; at += 2) {
5499
- points.push(builder.addPoint(path24[at], path24[at + 1]));
5498
+ for (let at = 0; at < path25.length; at += 2) {
5499
+ points.push(builder.addPoint(path25[at], path25[at + 1]));
5500
5500
  }
5501
5501
  builder.addPrimitive(points, { closed: false, kind: "poly" });
5502
5502
  trailIds.push(id);
@@ -5541,11 +5541,11 @@ function withDerivedVelocity(current, paths, velocityscale) {
5541
5541
  const ids = current.point.id?.data;
5542
5542
  const velocity = new Float32Array(current.pointCount * size);
5543
5543
  for (let index = 0; index < current.pointCount; index += 1) {
5544
- const path24 = ids ? paths.get(Number(ids[index])) : void 0;
5545
- if (!path24 || path24.length < 4) continue;
5546
- const last = path24.length;
5547
- velocity[index * size] = (path24[last - 2] - path24[last - 4]) * velocityscale;
5548
- velocity[index * size + 1] = (path24[last - 1] - path24[last - 3]) * velocityscale;
5544
+ const path25 = ids ? paths.get(Number(ids[index])) : void 0;
5545
+ if (!path25 || path25.length < 4) continue;
5546
+ const last = path25.length;
5547
+ velocity[index * size] = (path25[last - 2] - path25[last - 4]) * velocityscale;
5548
+ velocity[index * size + 1] = (path25[last - 1] - path25[last - 3]) * velocityscale;
5549
5549
  }
5550
5550
  return {
5551
5551
  ...current,
@@ -7063,16 +7063,16 @@ function cloneValue(value, ancestors) {
7063
7063
  ancestors.delete(value);
7064
7064
  return Object.freeze(clone);
7065
7065
  }
7066
- function runtimeValue(value, type, allowUndefined, path24) {
7066
+ function runtimeValue(value, type, allowUndefined, path25) {
7067
7067
  if (value === void 0) {
7068
7068
  if (allowUndefined) return void 0;
7069
- misuse("runtime/value-type", `${path24} requires a ${type} value`);
7069
+ misuse("runtime/value-type", `${path25} requires a ${type} value`);
7070
7070
  }
7071
7071
  const tupleLength = TUPLE_LENGTHS[type];
7072
7072
  const valid = type === "float" && typeof value === "number" && Number.isFinite(value) || type === "int" && typeof value === "number" && Number.isInteger(value) || type === "bool" && typeof value === "boolean" || type === "string" && typeof value === "string" || tupleLength !== void 0 && Array.isArray(value) && value.length === tupleLength && value.every(
7073
7073
  (item) => type.endsWith("i") ? typeof item === "number" && Number.isInteger(item) : typeof item === "number" && Number.isFinite(item)
7074
7074
  ) || !tupleLength && type !== "float" && type !== "int" && type !== "bool" && type !== "string";
7075
- if (!valid) misuse("runtime/value-type", `${path24} is not a valid ${type}`);
7075
+ if (!valid) misuse("runtime/value-type", `${path25} is not a valid ${type}`);
7076
7076
  return snapshot(value);
7077
7077
  }
7078
7078
  var PREFLIGHT_WARNING_CODES, RUN_TOLERATED_CODES, MutableAbortSignal, Runtime, Graph, TUPLE_LENGTHS;
@@ -8352,32 +8352,32 @@ function extractNodeDefinition(source, fileName, ts4) {
8352
8352
  diagnostics: [error.diagnostic]
8353
8353
  };
8354
8354
  }
8355
- function read(node, path24, depth) {
8355
+ function read(node, path25, depth) {
8356
8356
  if (++visited > MAX_NODES)
8357
8357
  throw extraction(
8358
8358
  node,
8359
8359
  "definition/size-limit",
8360
8360
  "Definition exceeds 10,000 syntax nodes",
8361
- path24
8361
+ path25
8362
8362
  );
8363
8363
  if (depth > MAX_DEPTH)
8364
8364
  throw extraction(
8365
8365
  node,
8366
8366
  "definition/depth-limit",
8367
8367
  "Definition exceeds 32 levels",
8368
- path24
8368
+ path25
8369
8369
  );
8370
8370
  if (ts4.isParenthesizedExpression(node) || ts4.isSatisfiesExpression(node))
8371
- return read(node.expression, path24, depth + 1);
8371
+ return read(node.expression, path25, depth + 1);
8372
8372
  if (ts4.isAsExpression(node)) {
8373
8373
  if (node.type.getText(file) !== "const")
8374
8374
  throw extraction(
8375
8375
  node.type,
8376
8376
  "definition/non-literal",
8377
8377
  "Only 'as const' assertions are allowed",
8378
- path24
8378
+ path25
8379
8379
  );
8380
- return read(node.expression, path24, depth + 1);
8380
+ return read(node.expression, path25, depth + 1);
8381
8381
  }
8382
8382
  if (ts4.isStringLiteral(node) || ts4.isNoSubstitutionTemplateLiteral(node))
8383
8383
  return node.text;
@@ -8394,9 +8394,9 @@ function extractNodeDefinition(source, fileName, ts4) {
8394
8394
  element,
8395
8395
  "definition/spread",
8396
8396
  "Spreads are not allowed",
8397
- `${path24}.${index}`
8397
+ `${path25}.${index}`
8398
8398
  );
8399
- return read(element, `${path24}.${index}`, depth + 1);
8399
+ return read(element, `${path25}.${index}`, depth + 1);
8400
8400
  });
8401
8401
  if (ts4.isObjectLiteralExpression(node)) {
8402
8402
  const object = /* @__PURE__ */ Object.create(null);
@@ -8407,14 +8407,14 @@ function extractNodeDefinition(source, fileName, ts4) {
8407
8407
  property,
8408
8408
  "definition/spread",
8409
8409
  "Spreads are not allowed",
8410
- path24
8410
+ path25
8411
8411
  );
8412
8412
  if (!ts4.isPropertyAssignment(property) || property.name && ts4.isComputedPropertyName(property.name))
8413
8413
  throw extraction(
8414
8414
  property,
8415
8415
  "definition/non-literal",
8416
8416
  "Only explicit properties are allowed",
8417
- path24
8417
+ path25
8418
8418
  );
8419
8419
  const key2 = ts4.isIdentifier(property.name) || ts4.isStringLiteral(property.name) || ts4.isNumericLiteral(property.name) ? property.name.text : void 0;
8420
8420
  if (key2 === void 0)
@@ -8422,17 +8422,17 @@ function extractNodeDefinition(source, fileName, ts4) {
8422
8422
  property,
8423
8423
  "definition/non-literal",
8424
8424
  "Property name must be literal",
8425
- path24
8425
+ path25
8426
8426
  );
8427
8427
  if (keys.has(key2))
8428
8428
  throw extraction(
8429
8429
  property.name,
8430
8430
  "definition/duplicate-key",
8431
8431
  `Duplicate key ${key2}`,
8432
- `${path24}.${key2}`
8432
+ `${path25}.${key2}`
8433
8433
  );
8434
8434
  keys.add(key2);
8435
- object[key2] = read(property.initializer, `${path24}.${key2}`, depth + 1);
8435
+ object[key2] = read(property.initializer, `${path25}.${key2}`, depth + 1);
8436
8436
  }
8437
8437
  return object;
8438
8438
  }
@@ -8440,19 +8440,19 @@ function extractNodeDefinition(source, fileName, ts4) {
8440
8440
  node,
8441
8441
  "definition/non-literal",
8442
8442
  `Unsupported ${ts4.SyntaxKind[node.kind]}`,
8443
- path24
8443
+ path25
8444
8444
  );
8445
8445
  }
8446
8446
  function location(node) {
8447
8447
  const point = file.getLineAndCharacterOfPosition(node.getStart(file));
8448
8448
  return { line: point.line + 1, column: point.character + 1 };
8449
8449
  }
8450
- function extraction(node, code, message2, path24) {
8450
+ function extraction(node, code, message2, path25) {
8451
8451
  return new ExtractionFailure({
8452
8452
  phase: "definition",
8453
8453
  code,
8454
8454
  message: message2,
8455
- path: path24,
8455
+ path: path25,
8456
8456
  file: fileName,
8457
8457
  ...location(node)
8458
8458
  });
@@ -9568,17 +9568,16 @@ export function execute(node, graph) {
9568
9568
  import fs6 from "node:fs/promises";
9569
9569
  import path6 from "node:path";
9570
9570
  function createNodeFileCapability(project) {
9571
- const rootPromise = realOrSelf(path6.resolve(project.resolve(".")));
9572
- const within3 = async (relative, what) => {
9573
- const clean = relative.replace(/^\.?\//, "");
9574
- const full = path6.resolve(project.resolve(clean));
9575
- const root = await rootPromise;
9576
- const real = await realOrSelf(full);
9577
- if (real !== root && !real.startsWith(`${root}${path6.sep}`))
9578
- throw new Error(`files: ${what} outside the project is refused \u2014 ${relative}`);
9579
- return full;
9571
+ const within3 = (relative, what) => {
9572
+ try {
9573
+ return project.resolve(relative.replace(/^\.?\//, ""));
9574
+ } catch (error) {
9575
+ throw new Error(
9576
+ `files: ${what} outside the project is refused \u2014 ${relative} (${error instanceof Error ? error.message : String(error)})`
9577
+ );
9578
+ }
9580
9579
  };
9581
- const cacheOnly = async (relative) => {
9580
+ const cacheOnly = (relative) => {
9582
9581
  const clean = relative.replace(/^\.?\//, "");
9583
9582
  if (!clean.startsWith(".cascade-cache/"))
9584
9583
  throw new Error(
@@ -9592,17 +9591,17 @@ function createNodeFileCapability(project) {
9592
9591
  return {
9593
9592
  async read(relative, options2) {
9594
9593
  live(options2);
9595
- return new Uint8Array(await fs6.readFile(await within3(relative, "reading")));
9594
+ return new Uint8Array(await fs6.readFile(within3(relative, "reading")));
9596
9595
  },
9597
9596
  async write(relative, data, options2) {
9598
9597
  live(options2);
9599
- const full = await cacheOnly(relative);
9598
+ const full = cacheOnly(relative);
9600
9599
  await fs6.mkdir(path6.dirname(full), { recursive: true });
9601
9600
  await fs6.writeFile(full, data);
9602
9601
  },
9603
9602
  async list(relative, options2) {
9604
9603
  live(options2);
9605
- const full = await within3(relative, "listing");
9604
+ const full = within3(relative, "listing");
9606
9605
  try {
9607
9606
  const entries2 = await fs6.readdir(full, { withFileTypes: true });
9608
9607
  return entries2.map((entry) => entry.isDirectory() ? `${entry.name}/` : entry.name).sort();
@@ -9614,7 +9613,7 @@ function createNodeFileCapability(project) {
9614
9613
  async stat(relative, options2) {
9615
9614
  live(options2);
9616
9615
  try {
9617
- const stats = await fs6.stat(await within3(relative, "reading"));
9616
+ const stats = await fs6.stat(within3(relative, "reading"));
9618
9617
  return { size: stats.size, modifiedAt: stats.mtimeMs };
9619
9618
  } catch (error) {
9620
9619
  if (error.code === "ENOENT") return null;
@@ -9623,22 +9622,6 @@ function createNodeFileCapability(project) {
9623
9622
  }
9624
9623
  };
9625
9624
  }
9626
- async function realOrSelf(full) {
9627
- let current = full;
9628
- const tail = [];
9629
- for (let depth = 0; depth < 64; depth += 1) {
9630
- try {
9631
- const real = await fs6.realpath(current);
9632
- return tail.length ? path6.join(real, ...tail.reverse()) : real;
9633
- } catch {
9634
- const parent = path6.dirname(current);
9635
- if (parent === current) return full;
9636
- tail.push(path6.basename(current));
9637
- current = parent;
9638
- }
9639
- }
9640
- return full;
9641
- }
9642
9625
  var init_nodeFiles = __esm({
9643
9626
  "src/cli/nodeFiles.ts"() {
9644
9627
  "use strict";
@@ -11038,6 +11021,8 @@ var init_git = __esm({
11038
11021
 
11039
11022
  // server/src/routes/graph.ts
11040
11023
  import { Router } from "express";
11024
+ import fs14 from "node:fs/promises";
11025
+ import path17 from "node:path";
11041
11026
  function handleError(res, err) {
11042
11027
  if (err instanceof PathSafetyError) {
11043
11028
  res.status(400).json({ error: err.message });
@@ -11090,6 +11075,42 @@ function createGraphRouter(project) {
11090
11075
  handleError(res, err);
11091
11076
  }
11092
11077
  });
11078
+ router.get("/:filename/workspace", async (req, res) => {
11079
+ try {
11080
+ const target = workspacePath(project, req.params.filename);
11081
+ await fs14.access(project.resolve(req.params.filename));
11082
+ let content;
11083
+ try {
11084
+ content = await fs14.readFile(target, "utf-8");
11085
+ } catch (err) {
11086
+ if (err?.code === "ENOENT") {
11087
+ res.json(null);
11088
+ return;
11089
+ }
11090
+ throw err;
11091
+ }
11092
+ res.type("application/json").send(content);
11093
+ } catch (err) {
11094
+ handleError(res, err);
11095
+ }
11096
+ });
11097
+ router.put("/:filename/workspace", async (req, res) => {
11098
+ try {
11099
+ const body = req.body;
11100
+ if (!body || typeof body !== "object" || Array.isArray(body) || !body.layout || typeof body.layout !== "object") {
11101
+ res.status(400).json({ error: "a workspace is an object with a layout" });
11102
+ return;
11103
+ }
11104
+ const slot = req.query.slot === "failed" ? "failed" : null;
11105
+ const target = workspacePath(project, req.params.filename, slot);
11106
+ const temporary = `${target}.${process.pid}.tmp`;
11107
+ await fs14.writeFile(temporary, JSON.stringify(body, null, 1), "utf-8");
11108
+ await fs14.rename(temporary, target);
11109
+ res.json({ ok: true, file: path17.basename(target) });
11110
+ } catch (err) {
11111
+ handleError(res, err);
11112
+ }
11113
+ });
11093
11114
  router.get("/:filename/versions", async (req, res) => {
11094
11115
  try {
11095
11116
  const filename = req.params.filename;
@@ -11127,6 +11148,13 @@ function createGraphRouter(project) {
11127
11148
  });
11128
11149
  return router;
11129
11150
  }
11151
+ function workspacePath(project, filename, slot = null) {
11152
+ if (!filename.endsWith(".cascade") || filename !== path17.basename(filename)) {
11153
+ throw new PathSafetyError(`not a .cascade file: ${filename}`);
11154
+ }
11155
+ const stem = filename.slice(0, -".cascade".length);
11156
+ return project.resolve(`${stem}.workspace${slot ? `.${slot}` : ""}.json`);
11157
+ }
11130
11158
  function saveCommitMessage(filename, at = /* @__PURE__ */ new Date()) {
11131
11159
  const time = at.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", hour12: false });
11132
11160
  return `Saved ${filename} ${time}`;
@@ -11143,8 +11171,8 @@ var init_graph = __esm({
11143
11171
  // server/src/routes/assets.ts
11144
11172
  import { Router as Router2 } from "express";
11145
11173
  import multer from "multer";
11146
- import path17 from "path";
11147
- import fs14 from "fs/promises";
11174
+ import path18 from "path";
11175
+ import fs15 from "fs/promises";
11148
11176
  function handleError2(res, err) {
11149
11177
  if (err instanceof PathSafetyError) {
11150
11178
  res.status(400).json({ error: err.message });
@@ -11160,8 +11188,8 @@ function createAssetsRouter(project) {
11160
11188
  let assetType = "data";
11161
11189
  if (file.mimetype.startsWith("image/")) assetType = "images";
11162
11190
  else if (file.mimetype.startsWith("audio/")) assetType = "audio";
11163
- const dir = path17.join(assetsRoot, assetType);
11164
- await fs14.mkdir(dir, { recursive: true });
11191
+ const dir = path18.join(assetsRoot, assetType);
11192
+ await fs15.mkdir(dir, { recursive: true });
11165
11193
  cb(null, dir);
11166
11194
  },
11167
11195
  filename: (_req, file, cb) => cb(null, file.originalname)
@@ -11172,7 +11200,7 @@ function createAssetsRouter(project) {
11172
11200
  res.status(400).json({ error: "no file uploaded" });
11173
11201
  return;
11174
11202
  }
11175
- const relativePath = path17.relative(assetsRoot, req.file.path);
11203
+ const relativePath = path18.relative(assetsRoot, req.file.path);
11176
11204
  res.json({
11177
11205
  id: req.file.filename,
11178
11206
  path: `./assets/${relativePath}`,
@@ -11198,7 +11226,7 @@ function createAssetsRouter(project) {
11198
11226
  try {
11199
11227
  const rel = req.params.assetPath?.join("/") ?? "";
11200
11228
  const full = resolveWithinRoot(assetsRoot, rel);
11201
- await fs14.unlink(full);
11229
+ await fs15.unlink(full);
11202
11230
  res.json({ ok: true });
11203
11231
  } catch (err) {
11204
11232
  handleError2(res, err);
@@ -11214,10 +11242,10 @@ var init_assets = __esm({
11214
11242
  });
11215
11243
 
11216
11244
  // server/src/nodeWatch.ts
11217
- import fs15 from "node:fs";
11218
- import path18 from "node:path";
11245
+ import fs16 from "node:fs";
11246
+ import path19 from "node:path";
11219
11247
  function moduleNameFor(relative) {
11220
- const segments = relative.split(path18.sep).filter(Boolean);
11248
+ const segments = relative.split(path19.sep).filter(Boolean);
11221
11249
  if (segments.length < 2) return null;
11222
11250
  const name = segments[0];
11223
11251
  if (!name || name.startsWith(".")) return null;
@@ -11239,9 +11267,9 @@ function createNodeWatcher(project) {
11239
11267
  );
11240
11268
  }
11241
11269
  function watch(directory, scope) {
11242
- if (!fs15.existsSync(directory)) return;
11270
+ if (!fs16.existsSync(directory)) return;
11243
11271
  try {
11244
- const watcher = fs15.watch(directory, { recursive: true }, (_event, filename) => {
11272
+ const watcher = fs16.watch(directory, { recursive: true }, (_event, filename) => {
11245
11273
  if (!filename) return;
11246
11274
  if (scope === "shared") {
11247
11275
  emit(ALL_MODULES);
@@ -11258,8 +11286,8 @@ function createNodeWatcher(project) {
11258
11286
  }
11259
11287
  watch(project.resolve("nodes"), "project");
11260
11288
  try {
11261
- const sharedNodes = fs15.realpathSync(path18.join(project.root, "shared"));
11262
- watch(path18.join(sharedNodes, "nodes"), "shared");
11289
+ const sharedNodes = fs16.realpathSync(path19.join(project.root, "shared"));
11290
+ watch(path19.join(sharedNodes, "nodes"), "shared");
11263
11291
  } catch {
11264
11292
  }
11265
11293
  return {
@@ -11929,15 +11957,15 @@ var init_eventBuffer = __esm({
11929
11957
  });
11930
11958
 
11931
11959
  // server/src/agent/allowlist.ts
11932
- import fs16 from "node:fs";
11960
+ import fs17 from "node:fs";
11933
11961
  import os4 from "node:os";
11934
- import path19 from "node:path";
11962
+ import path20 from "node:path";
11935
11963
  function allowlistHint(root, alias) {
11936
11964
  return `Add this under "commands" in ${root}/cascade.json: "${alias}": "<path>"`;
11937
11965
  }
11938
11966
  function readManifest(root) {
11939
11967
  try {
11940
- return JSON.parse(fs16.readFileSync(path19.join(root, "cascade.json"), "utf8"));
11968
+ return JSON.parse(fs17.readFileSync(path20.join(root, "cascade.json"), "utf8"));
11941
11969
  } catch {
11942
11970
  return {};
11943
11971
  }
@@ -11957,8 +11985,8 @@ function agentExtraArgs(root, alias) {
11957
11985
  }
11958
11986
  function executableFile2(candidate) {
11959
11987
  try {
11960
- fs16.accessSync(candidate, fs16.constants.X_OK);
11961
- return fs16.statSync(candidate).isFile() ? fs16.realpathSync(candidate) : null;
11988
+ fs17.accessSync(candidate, fs17.constants.X_OK);
11989
+ return fs17.statSync(candidate).isFile() ? fs17.realpathSync(candidate) : null;
11962
11990
  } catch {
11963
11991
  return null;
11964
11992
  }
@@ -11966,10 +11994,10 @@ function executableFile2(candidate) {
11966
11994
  function resolveExecutable2(target) {
11967
11995
  if (typeof target !== "string" || !target || target.includes("\0")) return null;
11968
11996
  const expanded = target.replace(/^~(?=\/|$)/, os4.homedir());
11969
- if (path19.isAbsolute(expanded)) return executableFile2(expanded);
11997
+ if (path20.isAbsolute(expanded)) return executableFile2(expanded);
11970
11998
  if (!/[\\/]/.test(expanded)) {
11971
- for (const dir of (process.env.PATH ?? "").split(path19.delimiter)) {
11972
- const found = executableFile2(path19.join(dir, expanded));
11999
+ for (const dir of (process.env.PATH ?? "").split(path20.delimiter)) {
12000
+ const found = executableFile2(path20.join(dir, expanded));
11973
12001
  if (found) return found;
11974
12002
  }
11975
12003
  return null;
@@ -12420,7 +12448,7 @@ var init_agent = __esm({
12420
12448
  });
12421
12449
 
12422
12450
  // server/src/graphWatch.ts
12423
- import fs17 from "node:fs";
12451
+ import fs18 from "node:fs";
12424
12452
  function graphFileFor(relative) {
12425
12453
  const segments = String(relative).split(/[\\/]/).filter(Boolean);
12426
12454
  if (segments.length !== 1) return null;
@@ -12445,7 +12473,7 @@ function createGraphWatcher(project) {
12445
12473
  );
12446
12474
  }
12447
12475
  try {
12448
- watcher = fs17.watch(project.root, { recursive: false }, (_event, filename) => {
12476
+ watcher = fs18.watch(project.root, { recursive: false }, (_event, filename) => {
12449
12477
  if (!filename) return;
12450
12478
  const graph = graphFileFor(String(filename));
12451
12479
  if (graph) emit(graph);
@@ -12631,8 +12659,8 @@ var init_net = __esm({
12631
12659
  // server/src/routes/media.ts
12632
12660
  import express, { Router as Router11 } from "express";
12633
12661
  import crypto2 from "crypto";
12634
- import fs18 from "fs";
12635
- import path20 from "path";
12662
+ import fs19 from "fs";
12663
+ import path21 from "path";
12636
12664
  async function loadSharp() {
12637
12665
  if (sharpModule) return sharpModule;
12638
12666
  if (sharpError) throw new Error(sharpError);
@@ -12719,11 +12747,11 @@ function fieldToGrey(array) {
12719
12747
  }
12720
12748
  function contentAddressed2(rel, body) {
12721
12749
  const hash2 = crypto2.createHash("sha1").update(body).digest("hex").slice(0, 12);
12722
- const dir = path20.posix.dirname(rel);
12723
- const name = path20.posix.basename(rel);
12750
+ const dir = path21.posix.dirname(rel);
12751
+ const name = path21.posix.basename(rel);
12724
12752
  const [stem, ...rest] = name.split(".");
12725
12753
  const tail = rest.length ? `.${rest.join(".")}` : "";
12726
- return path20.posix.join(dir, `${stem}.${hash2}${tail}`);
12754
+ return path21.posix.join(dir, `${stem}.${hash2}${tail}`);
12727
12755
  }
12728
12756
  function clampInt(value, min, max) {
12729
12757
  const n = Number(value);
@@ -12745,8 +12773,8 @@ function createMediaRouter(project) {
12745
12773
  try {
12746
12774
  const target = req.query.exact === "1" ? rel : contentAddressed2(rel, req.body);
12747
12775
  const full = project.resolve(target);
12748
- await fs18.promises.mkdir(path20.dirname(full), { recursive: true });
12749
- await fs18.promises.writeFile(full, req.body);
12776
+ await fs19.promises.mkdir(path21.dirname(full), { recursive: true });
12777
+ await fs19.promises.writeFile(full, req.body);
12750
12778
  res.json({ ok: true, path: target, bytes: req.body.length });
12751
12779
  } catch (err) {
12752
12780
  res.status(err instanceof PathSafetyError ? 400 : 500).json({ error: String(err) });
@@ -12763,13 +12791,13 @@ function createMediaRouter(project) {
12763
12791
  }
12764
12792
  let stat;
12765
12793
  try {
12766
- stat = await fs18.promises.stat(full);
12794
+ stat = await fs19.promises.stat(full);
12767
12795
  if (!stat.isFile()) throw new Error("not a file");
12768
12796
  } catch {
12769
12797
  res.status(404).json({ error: `no such file: ${rel}` });
12770
12798
  return;
12771
12799
  }
12772
- const ext = path20.extname(full).toLowerCase();
12800
+ const ext = path21.extname(full).toLowerCase();
12773
12801
  const width = clampInt(req.query.w, 1, 2e4);
12774
12802
  const quality = clampInt(req.query.q, 1, 100) ?? 82;
12775
12803
  const requested = String(req.query.fmt ?? "").toLowerCase();
@@ -12783,7 +12811,7 @@ function createMediaRouter(project) {
12783
12811
  }
12784
12812
  if (!raw && ext === ".npy") {
12785
12813
  try {
12786
- const array = readNpy(await fs18.promises.readFile(full));
12814
+ const array = readNpy(await fs19.promises.readFile(full));
12787
12815
  const { pixels, min, max } = fieldToGrey(array);
12788
12816
  res.setHeader("X-Field-Shape", `${array.height}x${array.width}`);
12789
12817
  res.setHeader("X-Field-Range", `${min}:${max}`);
@@ -12814,7 +12842,7 @@ function createMediaRouter(project) {
12814
12842
  if (raw || !RASTER.has(ext)) {
12815
12843
  res.type(MIME[ext] ?? "application/octet-stream");
12816
12844
  res.setHeader("Content-Length", String(stat.size));
12817
- fs18.createReadStream(full).pipe(res);
12845
+ fs19.createReadStream(full).pipe(res);
12818
12846
  return;
12819
12847
  }
12820
12848
  let format2 = requested;
@@ -12822,7 +12850,7 @@ function createMediaRouter(project) {
12822
12850
  if (format2 === "gif") {
12823
12851
  res.type("image/gif");
12824
12852
  res.setHeader("Content-Length", String(stat.size));
12825
- fs18.createReadStream(full).pipe(res);
12853
+ fs19.createReadStream(full).pipe(res);
12826
12854
  return;
12827
12855
  }
12828
12856
  try {
@@ -12889,11 +12917,11 @@ var init_media = __esm({
12889
12917
  });
12890
12918
 
12891
12919
  // server/src/build.ts
12892
- import fs19 from "node:fs";
12920
+ import fs20 from "node:fs";
12893
12921
  import { createHash } from "node:crypto";
12894
12922
  function buildId(distDir) {
12895
12923
  try {
12896
- const contents = fs19.readFileSync(`${distDir}/index.html`);
12924
+ const contents = fs20.readFileSync(`${distDir}/index.html`);
12897
12925
  return createHash("sha256").update(contents).digest("hex").slice(0, 16);
12898
12926
  } catch {
12899
12927
  return null;
@@ -12908,8 +12936,8 @@ var init_build = __esm({
12908
12936
  // server/src/index.ts
12909
12937
  import express2 from "express";
12910
12938
  import { createServer } from "http";
12911
- import path21 from "path";
12912
- import fs20 from "fs";
12939
+ import path22 from "path";
12940
+ import fs21 from "fs";
12913
12941
  import { fileURLToPath as fileURLToPath3 } from "url";
12914
12942
  function startServer(project, opts = {}) {
12915
12943
  const app = express2();
@@ -12976,7 +13004,7 @@ function startServer(project, opts = {}) {
12976
13004
  app.get("/api/build", (_req, res) => {
12977
13005
  res.json({ id: buildId(CASCADE_DIST) });
12978
13006
  });
12979
- if (fs20.existsSync(CASCADE_DIST)) {
13007
+ if (fs21.existsSync(CASCADE_DIST)) {
12980
13008
  app.use(express2.static(CASCADE_DIST, {
12981
13009
  setHeaders(res, filePath) {
12982
13010
  res.setHeader(
@@ -12988,7 +13016,7 @@ function startServer(project, opts = {}) {
12988
13016
  app.get("/{*splat}", (req, res, next) => {
12989
13017
  if (req.path.startsWith("/api") || req.path === "/health") return next();
12990
13018
  res.setHeader("Cache-Control", "no-store");
12991
- res.sendFile(path21.join(CASCADE_DIST, "index.html"));
13019
+ res.sendFile(path22.join(CASCADE_DIST, "index.html"));
12992
13020
  });
12993
13021
  } else {
12994
13022
  console.warn(`[cascade-server] no build found at ${CASCADE_DIST} \u2014 run "npm run build" in the Cascade repo first. API-only for now.`);
@@ -13021,18 +13049,18 @@ var init_src3 = __esm({
13021
13049
  init_build();
13022
13050
  init_service();
13023
13051
  __filename = fileURLToPath3(import.meta.url);
13024
- __dirname = path21.dirname(__filename);
13025
- CASCADE_DIST = path21.resolve(__dirname, "..", "..", "dist");
13052
+ __dirname = path22.dirname(__filename);
13053
+ CASCADE_DIST = path22.resolve(__dirname, "..", "..", "dist");
13026
13054
  }
13027
13055
  });
13028
13056
 
13029
13057
  // server/src/projectTemplate.ts
13030
- import fs21 from "node:fs";
13031
- import path22 from "node:path";
13058
+ import fs22 from "node:fs";
13059
+ import path23 from "node:path";
13032
13060
  function cascadeVersionRange() {
13033
13061
  try {
13034
13062
  const pkg = JSON.parse(
13035
- fs21.readFileSync(new URL("../../package.json", import.meta.url), "utf8")
13063
+ fs22.readFileSync(new URL("../../package.json", import.meta.url), "utf8")
13036
13064
  );
13037
13065
  const match = /^(\d+)\.(\d+)\./.exec(pkg.version ?? "");
13038
13066
  return match ? `^${match[1]}.${match[2]}.0` : "*";
@@ -13044,14 +13072,14 @@ function createProject(projectsRoot, name) {
13044
13072
  if (!PROJECT_NAME.test(name)) {
13045
13073
  throw new Error("Project names use lowercase letters, numbers, and hyphens (maximum 64 characters).");
13046
13074
  }
13047
- const root = path22.resolve(projectsRoot);
13048
- const directory = path22.resolve(root, name);
13049
- if (path22.dirname(directory) !== root) throw new Error("Project path escapes the configured projects directory.");
13050
- if (fs21.existsSync(directory)) throw new Error(`Already exists: ${directory}`);
13051
- fs21.mkdirSync(path22.join(directory, "nodes"), { recursive: true });
13052
- fs21.mkdirSync(path22.join(directory, "assets"));
13053
- writeJson(path22.join(directory, "cascade.json"), { name });
13054
- writeJson(path22.join(directory, "index.cascade"), {
13075
+ const root = path23.resolve(projectsRoot);
13076
+ const directory = path23.resolve(root, name);
13077
+ if (path23.dirname(directory) !== root) throw new Error("Project path escapes the configured projects directory.");
13078
+ if (fs22.existsSync(directory)) throw new Error(`Already exists: ${directory}`);
13079
+ fs22.mkdirSync(path23.join(directory, "nodes"), { recursive: true });
13080
+ fs22.mkdirSync(path23.join(directory, "assets"));
13081
+ writeJson(path23.join(directory, "cascade.json"), { name });
13082
+ writeJson(path23.join(directory, "index.cascade"), {
13055
13083
  name,
13056
13084
  version: "0.2",
13057
13085
  viewport: { x: 0, y: 0, zoom: 1 },
@@ -13059,7 +13087,7 @@ function createProject(projectsRoot, name) {
13059
13087
  connections: [],
13060
13088
  annotations: []
13061
13089
  });
13062
- writeJson(path22.join(directory, "package.json"), {
13090
+ writeJson(path23.join(directory, "package.json"), {
13063
13091
  name,
13064
13092
  private: true,
13065
13093
  type: "module",
@@ -13079,7 +13107,7 @@ function createProject(projectsRoot, name) {
13079
13107
  typescript: "^6.0.0"
13080
13108
  }
13081
13109
  });
13082
- writeJson(path22.join(directory, "tsconfig.json"), {
13110
+ writeJson(path23.join(directory, "tsconfig.json"), {
13083
13111
  compilerOptions: {
13084
13112
  target: "ES2022",
13085
13113
  module: "ESNext",
@@ -13089,8 +13117,8 @@ function createProject(projectsRoot, name) {
13089
13117
  },
13090
13118
  include: ["nodes/**/*.ts"]
13091
13119
  });
13092
- fs21.writeFileSync(path22.join(directory, ".gitignore"), ".cascade-cache/\nrenders/\nnode_modules/\n");
13093
- fs21.writeFileSync(path22.join(directory, "AGENTS.md"), `# Cascade project
13120
+ fs22.writeFileSync(path23.join(directory, ".gitignore"), ".cascade-cache/\nrenders/\nnode_modules/\n");
13121
+ fs22.writeFileSync(path23.join(directory, "AGENTS.md"), `# Cascade project
13094
13122
 
13095
13123
  Read \`node_modules/cascade/AGENTS.md\` for the framework itself. What follows is what an agent working in *this* project needs.
13096
13124
 
@@ -13245,14 +13273,14 @@ function createNode(projectRoot, name) {
13245
13273
  if (!NODE_NAME.test(name)) {
13246
13274
  throw new Error("Node names start with an uppercase letter and contain only letters or numbers.");
13247
13275
  }
13248
- const directory = path22.resolve(projectRoot, "nodes", name);
13249
- if (fs21.existsSync(directory)) throw new Error(`Already exists: ${directory}`);
13250
- fs21.mkdirSync(directory, { recursive: true });
13251
- fs21.writeFileSync(path22.join(directory, "index.ts"), nodeSource(name));
13276
+ const directory = path23.resolve(projectRoot, "nodes", name);
13277
+ if (fs22.existsSync(directory)) throw new Error(`Already exists: ${directory}`);
13278
+ fs22.mkdirSync(directory, { recursive: true });
13279
+ fs22.writeFileSync(path23.join(directory, "index.ts"), nodeSource(name));
13252
13280
  return directory;
13253
13281
  }
13254
13282
  function writeJson(file, value) {
13255
- fs21.writeFileSync(file, `${JSON.stringify(value, null, 2)}
13283
+ fs22.writeFileSync(file, `${JSON.stringify(value, null, 2)}
13256
13284
  `);
13257
13285
  }
13258
13286
  function nodeSource(name) {
@@ -13290,8 +13318,8 @@ __export(cliCommands_exports, {
13290
13318
  parseStudioArgs: () => parseStudioArgs,
13291
13319
  runStudioCli: () => runStudioCli
13292
13320
  });
13293
- import fs22 from "node:fs";
13294
- import path23 from "node:path";
13321
+ import fs23 from "node:fs";
13322
+ import path24 from "node:path";
13295
13323
  import { execFile as execFile5 } from "node:child_process";
13296
13324
  function parseStudioArgs(args) {
13297
13325
  const positional = [];
@@ -13346,7 +13374,7 @@ async function runStudioCli(args) {
13346
13374
  const config = loadConfig2();
13347
13375
  if (positional[0] === "projects") {
13348
13376
  if (positional[1]) {
13349
- saveConfig({ projectsRoot: path23.resolve(positional[1].replace(/^~(?=\/|$)/, process.env.HOME ?? "~")) });
13377
+ saveConfig({ projectsRoot: path24.resolve(positional[1].replace(/^~(?=\/|$)/, process.env.HOME ?? "~")) });
13350
13378
  console.log(`[cascade] projects root set to ${loadConfig2().projectsRoot} (${configPath()})`);
13351
13379
  } else console.log(loadConfig2().projectsRoot);
13352
13380
  return;
@@ -13365,9 +13393,9 @@ async function runStudioCli(args) {
13365
13393
  return;
13366
13394
  }
13367
13395
  let target = positional[0] ?? ".";
13368
- if (!fs22.existsSync(path23.resolve(target))) {
13369
- const projectTarget = path23.resolve(config.projectsRoot, target);
13370
- if (fs22.existsSync(projectTarget)) target = projectTarget;
13396
+ if (!fs23.existsSync(path24.resolve(target))) {
13397
+ const projectTarget = path24.resolve(config.projectsRoot, target);
13398
+ if (fs23.existsSync(projectTarget)) target = projectTarget;
13371
13399
  }
13372
13400
  const project = ProjectRoot.fromArg(target);
13373
13401
  const server = startServer(project, { host, port, trustedHosts });
@@ -13513,8 +13541,8 @@ var ExpressionEngine = class extends ExpressionEvaluator {
13513
13541
  super();
13514
13542
  this.graph = null;
13515
13543
  const scope = {
13516
- resolveParam: (from, path24) => this.resolveNodePath(from, path24),
13517
- nodeExists: (from, path24) => from.node(path24) !== null,
13544
+ resolveParam: (from, path25) => this.resolveNodePath(from, path25),
13545
+ nodeExists: (from, path25) => from.node(path25) !== null,
13518
13546
  inputNode: (from, index) => this.resolveInput(from, index)
13519
13547
  };
13520
13548
  this.setScope(scope);
@@ -13528,15 +13556,15 @@ var ExpressionEngine = class extends ExpressionEvaluator {
13528
13556
  /**
13529
13557
  * Resolve a path to get the parameter value
13530
13558
  */
13531
- resolveNodePath(fromNode, path24) {
13532
- const lastSlash = path24.lastIndexOf("/");
13533
- if (lastSlash === -1 || path24.startsWith("./")) {
13534
- const paramName2 = path24.replace("./", "");
13559
+ resolveNodePath(fromNode, path25) {
13560
+ const lastSlash = path25.lastIndexOf("/");
13561
+ if (lastSlash === -1 || path25.startsWith("./")) {
13562
+ const paramName2 = path25.replace("./", "");
13535
13563
  const prop2 = fromNode.props[paramName2];
13536
13564
  return prop2 ? { value: prop2.value } : null;
13537
13565
  }
13538
- const nodePath = path24.substring(0, lastSlash) || ".";
13539
- const paramName = path24.substring(lastSlash + 1);
13566
+ const nodePath = path25.substring(0, lastSlash) || ".";
13567
+ const paramName = path25.substring(lastSlash + 1);
13540
13568
  const targetNode = fromNode.node(nodePath);
13541
13569
  if (!targetNode) return null;
13542
13570
  const prop = targetNode.props[paramName];
@@ -13563,14 +13591,14 @@ var ExpressionEngine = class extends ExpressionEvaluator {
13563
13591
  for (const [propKey, prop] of Object.entries(node.props)) {
13564
13592
  if (!prop.expression) continue;
13565
13593
  const paths = this.extractDependencies(prop.expression);
13566
- for (const path24 of paths) {
13567
- if (!this.validatePath(node, path24)) {
13594
+ for (const path25 of paths) {
13595
+ if (!this.validatePath(node, path25)) {
13568
13596
  warnings.push({
13569
13597
  nodeId: node.id,
13570
13598
  nodePath: node.path(),
13571
13599
  propName: propKey,
13572
13600
  expression: prop.expression,
13573
- brokenPath: path24
13601
+ brokenPath: path25
13574
13602
  });
13575
13603
  }
13576
13604
  }
@@ -13611,24 +13639,24 @@ var CascadeContext = class {
13611
13639
  /**
13612
13640
  * Find a node by path (absolute or relative to pwd)
13613
13641
  */
13614
- node(path24) {
13642
+ node(path25) {
13615
13643
  if (!this._graph) return null;
13616
- if (path24.startsWith("/")) {
13617
- return this.resolveAbsolutePath(path24);
13644
+ if (path25.startsWith("/")) {
13645
+ return this.resolveAbsolutePath(path25);
13618
13646
  }
13619
13647
  if (this._pwd) {
13620
- return this._pwd.node(path24);
13648
+ return this._pwd.node(path25);
13621
13649
  }
13622
- return this.resolveAbsolutePath("/" + path24);
13650
+ return this.resolveAbsolutePath("/" + path25);
13623
13651
  }
13624
13652
  /**
13625
13653
  * Find a parameter by path (e.g., "/node1/radius")
13626
13654
  */
13627
- parm(path24) {
13628
- const lastSlash = path24.lastIndexOf("/");
13655
+ parm(path25) {
13656
+ const lastSlash = path25.lastIndexOf("/");
13629
13657
  if (lastSlash === -1) return null;
13630
- const nodePath = path24.substring(0, lastSlash) || "/";
13631
- const parmName = path24.substring(lastSlash + 1);
13658
+ const nodePath = path25.substring(0, lastSlash) || "/";
13659
+ const parmName = path25.substring(lastSlash + 1);
13632
13660
  const node = this.node(nodePath);
13633
13661
  return node?.parm(parmName) ?? null;
13634
13662
  }
@@ -13654,12 +13682,12 @@ var CascadeContext = class {
13654
13682
  /**
13655
13683
  * Navigate to a path (like cd command)
13656
13684
  */
13657
- cd(path24) {
13658
- if (path24 === "/") {
13685
+ cd(path25) {
13686
+ if (path25 === "/") {
13659
13687
  this._pwd = null;
13660
13688
  return;
13661
13689
  }
13662
- const target = this.node(path24);
13690
+ const target = this.node(path25);
13663
13691
  if (target && target.isNetwork()) {
13664
13692
  this._pwd = target;
13665
13693
  }
@@ -13757,22 +13785,22 @@ var CascadeContext = class {
13757
13785
  /**
13758
13786
  * Get parameter value as number (shorthand)
13759
13787
  */
13760
- ch(path24) {
13761
- const p = this.parm(path24);
13788
+ ch(path25) {
13789
+ const p = this.parm(path25);
13762
13790
  return p?.evalAsFloat() ?? 0;
13763
13791
  }
13764
13792
  /**
13765
13793
  * Get parameter value as string (shorthand)
13766
13794
  */
13767
- chs(path24) {
13768
- const p = this.parm(path24);
13795
+ chs(path25) {
13796
+ const p = this.parm(path25);
13769
13797
  return p?.evalAsString() ?? "";
13770
13798
  }
13771
13799
  /**
13772
13800
  * Get parameter value as vector (shorthand)
13773
13801
  */
13774
- chv(path24) {
13775
- const p = this.parm(path24);
13802
+ chv(path25) {
13803
+ const p = this.parm(path25);
13776
13804
  const val = p?.eval();
13777
13805
  if (Array.isArray(val)) return val;
13778
13806
  if (typeof val === "number") return [val];
@@ -13809,9 +13837,9 @@ var CascadeContext = class {
13809
13837
  return this.fframe();
13810
13838
  }
13811
13839
  // ============ Internal Helpers ============
13812
- resolveAbsolutePath(path24) {
13840
+ resolveAbsolutePath(path25) {
13813
13841
  if (!this._graph) return null;
13814
- const segments = path24.replace(/^\//, "").split("/").filter((s) => s.length > 0);
13842
+ const segments = path25.replace(/^\//, "").split("/").filter((s) => s.length > 0);
13815
13843
  if (segments.length === 0) {
13816
13844
  return null;
13817
13845
  }
@@ -13967,7 +13995,7 @@ async function runGraph(options2) {
13967
13995
  }
13968
13996
 
13969
13997
  // src/cli/index.ts
13970
- import * as fs23 from "fs/promises";
13998
+ import * as fs24 from "fs/promises";
13971
13999
  import { readFileSync } from "node:fs";
13972
14000
 
13973
14001
  // src/cli/supervise.ts
@@ -14127,7 +14155,7 @@ image output nothing downstream consumes, or per output of --entry-node.
14127
14155
  process.exit(1);
14128
14156
  }
14129
14157
  try {
14130
- await fs23.access(fileArg);
14158
+ await fs24.access(fileArg);
14131
14159
  } catch {
14132
14160
  console.error(`Error: File not found: ${fileArg}`);
14133
14161
  process.exit(1);