fiberpath 0.7.3__tar.gz → 0.7.4__tar.gz

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 (355) hide show
  1. {fiberpath-0.7.3 → fiberpath-0.7.4}/.github/workflows/gui-ci.yml +3 -0
  2. {fiberpath-0.7.3 → fiberpath-0.7.4}/.github/workflows/release.yml +15 -12
  3. {fiberpath-0.7.3 → fiberpath-0.7.4}/CHANGELOG.md +17 -0
  4. {fiberpath-0.7.3 → fiberpath-0.7.4}/PKG-INFO +2 -2
  5. {fiberpath-0.7.3 → fiberpath-0.7.4}/README.md +1 -1
  6. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/index.md +7 -7
  7. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/config/schemas.py +9 -1
  8. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/config/validator.py +9 -2
  9. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/gcode/generator.py +1 -0
  10. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/planning/validators.py +10 -0
  11. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_api/routes/simulate.py +8 -1
  12. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_api/routes/stream.py +6 -0
  13. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_cli/plot.py +3 -1
  14. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_cli/simulate.py +1 -1
  15. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_cli/stream.py +1 -1
  16. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/package-lock.json +2 -2
  17. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/package.json +2 -1
  18. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/helicalValidation.test.ts +70 -4
  19. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/helicalValidation.ts +21 -3
  20. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/schemas.ts +8 -5
  21. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/types/components.ts +5 -4
  22. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src-tauri/Cargo.lock +1 -1
  23. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src-tauri/Cargo.toml +1 -1
  24. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src-tauri/src/main.rs +23 -2
  25. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src-tauri/src/marlin.rs +99 -4
  26. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src-tauri/tauri.conf.json +1 -1
  27. {fiberpath-0.7.3 → fiberpath-0.7.4}/pyproject.toml +1 -1
  28. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/api/test_simulate_route.py +28 -0
  29. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/api/test_stream_route_errors.py +11 -0
  30. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/cli/test_cli_json.py +12 -0
  31. fiberpath-0.7.4/tests/config/test_validator.py +124 -0
  32. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/gcode/test_generator.py +11 -0
  33. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/planning/test_validators.py +11 -0
  34. {fiberpath-0.7.3 → fiberpath-0.7.4}/uv.lock +1 -1
  35. fiberpath-0.7.3/tests/config/test_validator.py +0 -55
  36. {fiberpath-0.7.3 → fiberpath-0.7.4}/.gitattributes +0 -0
  37. {fiberpath-0.7.3 → fiberpath-0.7.4}/.github/actions/setup-node/action.yml +0 -0
  38. {fiberpath-0.7.3 → fiberpath-0.7.4}/.github/actions/setup-python/action.yml +0 -0
  39. {fiberpath-0.7.3 → fiberpath-0.7.4}/.github/actions/setup-rust/action.yml +0 -0
  40. {fiberpath-0.7.3 → fiberpath-0.7.4}/.github/workflows/backend-ci.yml +0 -0
  41. {fiberpath-0.7.3 → fiberpath-0.7.4}/.github/workflows/ci-check.yml +0 -0
  42. {fiberpath-0.7.3 → fiberpath-0.7.4}/.github/workflows/dependency-audit.yml +0 -0
  43. {fiberpath-0.7.3 → fiberpath-0.7.4}/.github/workflows/gui-e2e-smoke.yml +0 -0
  44. {fiberpath-0.7.3 → fiberpath-0.7.4}/.github/workflows/gui-packaging.yml +0 -0
  45. {fiberpath-0.7.3 → fiberpath-0.7.4}/.gitignore +0 -0
  46. {fiberpath-0.7.3 → fiberpath-0.7.4}/.pre-commit-config.yaml +0 -0
  47. {fiberpath-0.7.3 → fiberpath-0.7.4}/.vscode/extensions.json +0 -0
  48. {fiberpath-0.7.3 → fiberpath-0.7.4}/.vscode/launch.json +0 -0
  49. {fiberpath-0.7.3 → fiberpath-0.7.4}/.vscode/settings.json +0 -0
  50. {fiberpath-0.7.3 → fiberpath-0.7.4}/LICENSE +0 -0
  51. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/.pages +0 -0
  52. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/architecture/.pages +0 -0
  53. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/architecture/axis-system.md +0 -0
  54. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/architecture/overview.md +0 -0
  55. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/development/.pages +0 -0
  56. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/development/ci-cd.md +0 -0
  57. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/development/contributing.md +0 -0
  58. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/development/dependency-policy.md +0 -0
  59. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/development/feature-backlog.md +0 -0
  60. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/development/packaging.md +0 -0
  61. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/development/release-process.md +0 -0
  62. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/development/roadmap.md +0 -0
  63. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/development/tooling.md +0 -0
  64. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/getting-started.md +0 -0
  65. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/guides/.pages +0 -0
  66. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/guides/axis-mapping.md +0 -0
  67. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/guides/marlin-streaming.md +0 -0
  68. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/guides/visualization.md +0 -0
  69. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/guides/wind-format.md +0 -0
  70. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/reference/.pages +0 -0
  71. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/reference/api.md +0 -0
  72. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/reference/concepts.md +0 -0
  73. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/reference/planner-math.md +0 -0
  74. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/troubleshooting.md +0 -0
  75. {fiberpath-0.7.3 → fiberpath-0.7.4}/docs/vm-testing.md +0 -0
  76. {fiberpath-0.7.3 → fiberpath-0.7.4}/examples/README.md +0 -0
  77. {fiberpath-0.7.3 → fiberpath-0.7.4}/examples/multi_layer/README.md +0 -0
  78. {fiberpath-0.7.3 → fiberpath-0.7.4}/examples/multi_layer/input.wind +0 -0
  79. {fiberpath-0.7.3 → fiberpath-0.7.4}/examples/rocketry/AvBay(470mm)single.wind +0 -0
  80. {fiberpath-0.7.3 → fiberpath-0.7.4}/examples/rocketry/AvBay(470mm)triple.gcode +0 -0
  81. {fiberpath-0.7.3 → fiberpath-0.7.4}/examples/rocketry/AvBay(470mm)triple.wind +0 -0
  82. {fiberpath-0.7.3 → fiberpath-0.7.4}/examples/rocketry/CarbonMotorTube(1295mm).gcode +0 -0
  83. {fiberpath-0.7.3 → fiberpath-0.7.4}/examples/rocketry/CarbonMotorTube(1295mm).wind +0 -0
  84. {fiberpath-0.7.3 → fiberpath-0.7.4}/examples/rocketry/MainChute(585mm).gcode +0 -0
  85. {fiberpath-0.7.3 → fiberpath-0.7.4}/examples/rocketry/MainChute(585mm).wind +0 -0
  86. {fiberpath-0.7.3 → fiberpath-0.7.4}/examples/simple_cylinder/README.md +0 -0
  87. {fiberpath-0.7.3 → fiberpath-0.7.4}/examples/simple_cylinder/input.wind +0 -0
  88. {fiberpath-0.7.3 → fiberpath-0.7.4}/examples/sized_simple_cylinder/README.md +0 -0
  89. {fiberpath-0.7.3 → fiberpath-0.7.4}/examples/sized_simple_cylinder/input.wind +0 -0
  90. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/__init__.py +0 -0
  91. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/config/__init__.py +0 -0
  92. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/execution/__init__.py +0 -0
  93. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/execution/marlin.py +0 -0
  94. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/gcode/__init__.py +0 -0
  95. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/gcode/dialects.py +0 -0
  96. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/math_utils.py +0 -0
  97. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/planning/__init__.py +0 -0
  98. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/planning/calculations.py +0 -0
  99. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/planning/exceptions.py +0 -0
  100. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/planning/helpers.py +0 -0
  101. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/planning/layer_strategies.py +0 -0
  102. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/planning/machine.py +0 -0
  103. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/planning/planner.py +0 -0
  104. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/simulation/__init__.py +0 -0
  105. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/simulation/simulator.py +0 -0
  106. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/visualization/__init__.py +0 -0
  107. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/visualization/export_json.py +0 -0
  108. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath/visualization/plotter.py +0 -0
  109. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_api/__init__.py +0 -0
  110. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_api/main.py +0 -0
  111. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_api/package-lock.json +0 -0
  112. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_api/path_policy.py +0 -0
  113. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_api/routes/__init__.py +0 -0
  114. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_api/routes/plan.py +0 -0
  115. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_api/routes/validate.py +0 -0
  116. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_api/schemas.py +0 -0
  117. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_cli/__init__.py +0 -0
  118. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_cli/__main__.py +0 -0
  119. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_cli/interactive.py +0 -0
  120. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_cli/main.py +0 -0
  121. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_cli/output.py +0 -0
  122. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_cli/plan.py +0 -0
  123. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_cli/validate.py +0 -0
  124. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/.gitignore +0 -0
  125. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/.nvmrc +0 -0
  126. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/.stylelintrc.json +0 -0
  127. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/README.md +0 -0
  128. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/bundled-cli/.gitkeep +0 -0
  129. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/docs/.pages +0 -0
  130. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/docs/architecture/.pages +0 -0
  131. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/docs/architecture/cli-integration.md +0 -0
  132. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/docs/architecture/state-management.md +0 -0
  133. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/docs/architecture/streaming-state.md +0 -0
  134. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/docs/architecture/tech-stack.md +0 -0
  135. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/docs/development.md +0 -0
  136. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/docs/guides/.pages +0 -0
  137. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/docs/guides/performance.md +0 -0
  138. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/docs/guides/schemas.md +0 -0
  139. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/docs/guides/styling.md +0 -0
  140. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/docs/overview.md +0 -0
  141. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/docs/reference/.pages +0 -0
  142. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/docs/reference/type-safety.md +0 -0
  143. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/docs/testing.md +0 -0
  144. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/generate-types.json +0 -0
  145. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/index.html +0 -0
  146. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/perf/bundle-baseline.json +0 -0
  147. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/schemas/wind-schema.json +0 -0
  148. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/scripts/check-bundle-budget.mjs +0 -0
  149. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/scripts/check-css-vars.mjs +0 -0
  150. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/App.tsx +0 -0
  151. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/CliHealthWarning.tsx +0 -0
  152. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/ErrorBoundary.test.tsx +0 -0
  153. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/ErrorBoundary.tsx +0 -0
  154. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/MenuBar.tsx +0 -0
  155. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StatusBar.test.tsx +0 -0
  156. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StatusBar.tsx +0 -0
  157. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/ConnectionSection.css +0 -0
  158. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/ConnectionSection.test.tsx +0 -0
  159. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/ConnectionSection.tsx +0 -0
  160. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/FileStreamingSection.css +0 -0
  161. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/FileStreamingSection.test.tsx +0 -0
  162. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/FileStreamingSection.tsx +0 -0
  163. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/KeyboardShortcuts.css +0 -0
  164. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/KeyboardShortcuts.test.tsx +0 -0
  165. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/KeyboardShortcuts.tsx +0 -0
  166. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/ManualControlSection.css +0 -0
  167. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/ManualControlSection.test.tsx +0 -0
  168. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/ManualControlSection.tsx +0 -0
  169. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/StreamControls.css +0 -0
  170. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/StreamControls.tsx +0 -0
  171. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/StreamLog.css +0 -0
  172. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/StreamLog.test.tsx +0 -0
  173. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/StreamLog.tsx +0 -0
  174. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/StreamTab.css +0 -0
  175. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/StreamTab/StreamTab.tsx +0 -0
  176. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/TabBar.test.tsx +0 -0
  177. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/TabBar.tsx +0 -0
  178. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/Toast/ToastContainer.css +0 -0
  179. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/Toast/ToastContainer.test.tsx +0 -0
  180. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/Toast/ToastContainer.tsx +0 -0
  181. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/canvas/CanvasControls.test.tsx +0 -0
  182. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/canvas/CanvasControls.tsx +0 -0
  183. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/canvas/CenterCanvas.tsx +0 -0
  184. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/canvas/LayerScrubber.test.tsx +0 -0
  185. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/canvas/LayerScrubber.tsx +0 -0
  186. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/canvas/VisualizationCanvas.tsx +0 -0
  187. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/dialogs/AboutDialog.tsx +0 -0
  188. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/dialogs/BaseDialog.test.tsx +0 -0
  189. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/dialogs/BaseDialog.tsx +0 -0
  190. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/dialogs/CliUnavailableDialog.tsx +0 -0
  191. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/dialogs/DiagnosticsDialog.test.tsx +0 -0
  192. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/dialogs/DiagnosticsDialog.tsx +0 -0
  193. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/dialogs/ExportConfirmationDialog.tsx +0 -0
  194. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/editors/HelicalLayerEditor.test.tsx +0 -0
  195. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/editors/HelicalLayerEditor.tsx +0 -0
  196. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/editors/HoopLayerEditor.test.tsx +0 -0
  197. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/editors/HoopLayerEditor.tsx +0 -0
  198. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/editors/LayerNumericField.tsx +0 -0
  199. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/editors/SkipLayerEditor.test.tsx +0 -0
  200. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/editors/SkipLayerEditor.tsx +0 -0
  201. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/forms/MachineSettingsForm.test.tsx +0 -0
  202. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/forms/MachineSettingsForm.tsx +0 -0
  203. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/forms/MandrelForm.test.tsx +0 -0
  204. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/forms/MandrelForm.tsx +0 -0
  205. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/forms/TowForm.test.tsx +0 -0
  206. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/forms/TowForm.tsx +0 -0
  207. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/layers/LayerRow.test.tsx +0 -0
  208. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/layers/LayerRow.tsx +0 -0
  209. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/layers/LayerStack.test.tsx +0 -0
  210. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/layers/LayerStack.tsx +0 -0
  211. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/panels/BottomPanel.tsx +0 -0
  212. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/panels/LeftPanel.tsx +0 -0
  213. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/panels/RightPanel.tsx +0 -0
  214. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/panels/panels.test.tsx +0 -0
  215. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/tabs/MainTab.test.tsx +0 -0
  216. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/components/tabs/MainTab.tsx +0 -0
  217. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/contexts/CliHealthContext.test.tsx +0 -0
  218. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/contexts/CliHealthContext.tsx +0 -0
  219. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/canvas/usePreviewGeneration.test.ts +0 -0
  220. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/canvas/usePreviewGeneration.ts +0 -0
  221. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/stream/useConnectionActions.test.ts +0 -0
  222. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/stream/useConnectionActions.ts +0 -0
  223. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/stream/useManualCommandActions.test.ts +0 -0
  224. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/stream/useManualCommandActions.ts +0 -0
  225. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/stream/useStreamingActions.test.ts +0 -0
  226. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/stream/useStreamingActions.ts +0 -0
  227. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/useCliHealth.test.ts +0 -0
  228. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/useCliHealth.ts +0 -0
  229. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/useDebouncedValue.test.ts +0 -0
  230. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/useDebouncedValue.ts +0 -0
  231. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/useFileOperations.test.tsx +0 -0
  232. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/useFileOperations.ts +0 -0
  233. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/useKeyboardShortcuts.test.ts +0 -0
  234. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/useKeyboardShortcuts.ts +0 -0
  235. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/useMenubarInteractions.test.tsx +0 -0
  236. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/useMenubarInteractions.ts +0 -0
  237. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/useStreamEvents.ts +0 -0
  238. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/useTheme.test.ts +0 -0
  239. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/hooks/useTheme.ts +0 -0
  240. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/layouts/MainLayout.test.tsx +0 -0
  241. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/layouts/MainLayout.tsx +0 -0
  242. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/commands.test.ts +0 -0
  243. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/commands.ts +0 -0
  244. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/constants.ts +0 -0
  245. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/fileOperations.test.ts +0 -0
  246. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/fileOperations.ts +0 -0
  247. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/marlin-api.test.ts +0 -0
  248. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/marlin-api.ts +0 -0
  249. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/menuConfig.test.ts +0 -0
  250. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/menuConfig.ts +0 -0
  251. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/numericFields.test.ts +0 -0
  252. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/numericFields.ts +0 -0
  253. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/recentFiles.test.ts +0 -0
  254. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/recentFiles.ts +0 -0
  255. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/retry.test.ts +0 -0
  256. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/retry.ts +0 -0
  257. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/schemas.test.ts +0 -0
  258. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/streamFeedback.test.ts +0 -0
  259. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/streamFeedback.ts +0 -0
  260. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/tauri-types.ts +0 -0
  261. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/toastMessages.ts +0 -0
  262. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/validation.test.ts +0 -0
  263. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/validation.ts +0 -0
  264. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/validationErrors.test.ts +0 -0
  265. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/lib/validationErrors.ts +0 -0
  266. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/main.tsx +0 -0
  267. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/stores/projectStore.test.ts +0 -0
  268. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/stores/projectStore.ts +0 -0
  269. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/stores/streamStore.ts +0 -0
  270. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/stores/toastStore.test.ts +0 -0
  271. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/stores/toastStore.ts +0 -0
  272. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/styles/buttons.css +0 -0
  273. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/styles/canvas.css +0 -0
  274. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/styles/dialogs.css +0 -0
  275. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/styles/forms.css +0 -0
  276. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/styles/index.css +0 -0
  277. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/styles/layout.css +0 -0
  278. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/styles/notifications.css +0 -0
  279. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/styles/panels.css +0 -0
  280. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/styles/reset.css +0 -0
  281. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/styles/tabs.css +0 -0
  282. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/styles/tokens.css +0 -0
  283. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/styles/typography.css +0 -0
  284. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/tests/integration/streamLifecycle.test.ts +0 -0
  285. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/tests/integration/workflows.test.ts +0 -0
  286. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/tests/renderWithProviders.tsx +0 -0
  287. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/tests/setup.ts +0 -0
  288. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/tests/storeUtils.ts +0 -0
  289. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/tests/tauriMocks.ts +0 -0
  290. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/types/converters.test.ts +0 -0
  291. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/types/converters.ts +0 -0
  292. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/types/project.test.ts +0 -0
  293. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/types/project.ts +0 -0
  294. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src/types/wind-schema.ts +0 -0
  295. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src-tauri/build.rs +0 -0
  296. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src-tauri/capabilities/default.json +0 -0
  297. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src-tauri/icons/128x128.png +0 -0
  298. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src-tauri/icons/256x256.png +0 -0
  299. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src-tauri/icons/32x32.png +0 -0
  300. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src-tauri/icons/512x512.png +0 -0
  301. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src-tauri/icons/icon.ico +0 -0
  302. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src-tauri/src/cli_path.rs +0 -0
  303. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/src-tauri/src/cli_process.rs +0 -0
  304. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/tsconfig.json +0 -0
  305. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/tsconfig.node.json +0 -0
  306. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/vite.config.ts +0 -0
  307. {fiberpath-0.7.3 → fiberpath-0.7.4}/fiberpath_gui/vitest.config.ts +0 -0
  308. {fiberpath-0.7.3 → fiberpath-0.7.4}/package-lock.json +0 -0
  309. {fiberpath-0.7.3 → fiberpath-0.7.4}/renovate.json +0 -0
  310. {fiberpath-0.7.3 → fiberpath-0.7.4}/scripts/ci/e2e-check-artifacts.ps1 +0 -0
  311. {fiberpath-0.7.3 → fiberpath-0.7.4}/scripts/ci/e2e-check-artifacts.sh +0 -0
  312. {fiberpath-0.7.3 → fiberpath-0.7.4}/scripts/ci/e2e-cli-smoke.ps1 +0 -0
  313. {fiberpath-0.7.3 → fiberpath-0.7.4}/scripts/ci/e2e-cli-smoke.sh +0 -0
  314. {fiberpath-0.7.3 → fiberpath-0.7.4}/scripts/ci/extract-package-runtime.ps1 +0 -0
  315. {fiberpath-0.7.3 → fiberpath-0.7.4}/scripts/ci/extract-package-runtime.sh +0 -0
  316. {fiberpath-0.7.3 → fiberpath-0.7.4}/scripts/ci/find-bundled-cli.ps1 +0 -0
  317. {fiberpath-0.7.3 → fiberpath-0.7.4}/scripts/ci/find-bundled-cli.sh +0 -0
  318. {fiberpath-0.7.3 → fiberpath-0.7.4}/scripts/export_schema.py +0 -0
  319. {fiberpath-0.7.3 → fiberpath-0.7.4}/scripts/freeze_cli.py +0 -0
  320. {fiberpath-0.7.3 → fiberpath-0.7.4}/scripts/generate_schema.py +0 -0
  321. {fiberpath-0.7.3 → fiberpath-0.7.4}/setup.cfg +0 -0
  322. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/api/test_path_policy.py +0 -0
  323. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/api/test_plan_route.py +0 -0
  324. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/api/test_stream_route.py +0 -0
  325. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/cli/test_axis_format.py +0 -0
  326. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/cli/test_cli_smoke.py +0 -0
  327. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/cli/test_stream_command.py +0 -0
  328. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/config/__init__.py +0 -0
  329. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/cyclone_reference_runs/inputs/helical-balanced.wind +0 -0
  330. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/cyclone_reference_runs/inputs/simple-hoop.wind +0 -0
  331. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/cyclone_reference_runs/inputs/skip-bias.wind +0 -0
  332. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/cyclone_reference_runs/outputs/helical-balanced/output.gcode +0 -0
  333. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/cyclone_reference_runs/outputs/helical-balanced/preview.png +0 -0
  334. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/cyclone_reference_runs/outputs/simple-hoop/output.gcode +0 -0
  335. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/cyclone_reference_runs/outputs/simple-hoop/preview.png +0 -0
  336. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/cyclone_reference_runs/outputs/skip-bias/output.gcode +0 -0
  337. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/cyclone_reference_runs/outputs/skip-bias/preview.png +0 -0
  338. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/execution/test_marlin_streamer.py +0 -0
  339. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/gcode/test_dialects.py +0 -0
  340. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/planning/_generate_fixtures.py +0 -0
  341. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/planning/fixtures/helical_layer.gcode +0 -0
  342. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/planning/fixtures/hoop_layer.gcode +0 -0
  343. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/planning/fixtures/hoop_only_program.gcode +0 -0
  344. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/planning/fixtures/skip_layer.gcode +0 -0
  345. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/planning/test_axis_mapping.py +0 -0
  346. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/planning/test_helpers_machine.py +0 -0
  347. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/planning/test_layer_strategies.py +0 -0
  348. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/planning/test_planner_smoke.py +0 -0
  349. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/simulation/test_simulator.py +0 -0
  350. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/simulation/test_simulator_axis_detection.py +0 -0
  351. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/tauri/test_marlin_integration.py +0 -0
  352. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/tauri/test_port_discovery.py +0 -0
  353. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/visualization/test_export_json.py +0 -0
  354. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/visualization/test_plotter.py +0 -0
  355. {fiberpath-0.7.3 → fiberpath-0.7.4}/tests/visualization/test_plotter_axis_detection.py +0 -0
@@ -89,3 +89,6 @@ jobs:
89
89
 
90
90
  - name: Run Clippy (Rust linter)
91
91
  run: npm run clippy
92
+
93
+ - name: Run Rust tests
94
+ run: npm run test:rust
@@ -135,16 +135,19 @@ jobs:
135
135
 
136
136
  let notes = '';
137
137
 
138
- // Desktop installers section
138
+ // Desktop installers section. Asset names match Tauri's bundle
139
+ // output: the productName "FiberPath GUI" yields a space that
140
+ // GitHub renders as `.` in the release asset filename, and the
141
+ // macOS dmg is built for Apple Silicon (aarch64) on macos-latest.
139
142
  notes += `## Desktop Installers\n\n`;
140
143
  notes += `**Windows:**\n`;
141
- notes += `- \`FiberPath_${version}_x64_en-US.msi\` (recommended)\n`;
142
- notes += `- \`FiberPath_${version}_x64-setup.exe\`\n\n`;
143
- notes += `**macOS:**\n`;
144
- notes += `- \`FiberPath_${version}_universal.dmg\`\n\n`;
144
+ notes += `- \`FiberPath.GUI_${version}_x64_en-US.msi\` (recommended)\n`;
145
+ notes += `- \`FiberPath.GUI_${version}_x64-setup.exe\`\n\n`;
146
+ notes += `**macOS (Apple Silicon):**\n`;
147
+ notes += `- \`FiberPath.GUI_${version}_aarch64.dmg\`\n\n`;
145
148
  notes += `**Linux:**\n`;
146
- notes += `- \`fiberpath_${version}_amd64.deb\`\n`;
147
- notes += `- \`fiberpath_${version}_amd64.AppImage\`\n\n`;
149
+ notes += `- \`FiberPath.GUI_${version}_amd64.deb\`\n`;
150
+ notes += `- \`FiberPath.GUI_${version}_amd64.AppImage\`\n\n`;
148
151
  notes += `📚 [Installation Guide](https://fiberpath.org/fiberpath/getting-started/) | `;
149
152
  notes += `🆘 [Troubleshooting](https://fiberpath.org/fiberpath/troubleshooting/)\n\n`;
150
153
 
@@ -165,11 +168,11 @@ jobs:
165
168
  run: |
166
169
  VERSION="${{ inputs.version }}"
167
170
  echo "Expected release assets for v$VERSION:"
168
- echo " - FiberPath_${VERSION}_x64_en-US.msi"
169
- echo " - FiberPath_${VERSION}_x64-setup.exe"
170
- echo " - FiberPath_${VERSION}_universal.dmg"
171
- echo " - fiberpath_${VERSION}_amd64.deb"
172
- echo " - fiberpath_${VERSION}_amd64.AppImage"
171
+ echo " - FiberPath.GUI_${VERSION}_x64_en-US.msi"
172
+ echo " - FiberPath.GUI_${VERSION}_x64-setup.exe"
173
+ echo " - FiberPath.GUI_${VERSION}_aarch64.dmg"
174
+ echo " - FiberPath.GUI_${VERSION}_amd64.deb"
175
+ echo " - FiberPath.GUI_${VERSION}_amd64.AppImage"
173
176
  echo "Assets will be uploaded by build-gui-installers workflow"
174
177
 
175
178
  - name: Create GitHub Release
@@ -8,6 +8,23 @@ The format is based on Keep a Changelog, and this project follows semantic versi
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.7.4] - 2026-06-25
12
+
13
+ ### Fixed
14
+
15
+ - Marlin desktop commands no longer hang forever if the underlying CLI subprocess dies or emits a non-JSON line: when the response reader stops, in-flight requests are failed (the command returns an error) instead of leaving the UI spinner stuck permanently.
16
+ - Desktop preview/validate temp files now use process- and counter-unique names, fixing rare collisions when commands fired in the same millisecond could delete or clobber each other's temp file (intermittent "file not found" / corrupted preview).
17
+ - The Marlin desktop integration now recovers if its helper subprocess dies: a dead process is detected and respawned on the next command instead of reusing it forever, and the subprocess is killed on app shutdown so it no longer lingers as an orphan holding the serial port open.
18
+ - Helical layer editor and save-gate validation now match the planner's bounds, so the editor no longer accepts values the backend rejects with a 422: `lockDegrees`, `leadInMM`, and `leadOutDegrees` must be positive (were allowed to be `0`), `skipIndex` must be a positive integer (the save gate allowed `0`), and the wind angle is constrained to `[1°, 89°]` (was `(0°, 90°)`; the engine clamps to `[1, 89]` because `90°` gives `cos = 0`).
19
+ - Helical layer geometry hint no longer renders "not divisible by pattern number (NaN)" while the Pattern Number field is mid-edit (emptied to `NaN`); no hint is shown until the field holds a valid positive integer.
20
+ - The streaming API (`POST /stream/`) now returns 400 for empty/whitespace-only G-code instead of 502; an empty program is a client error, not an upstream device failure (502 is reserved for genuine transport errors).
21
+ - Simulating a G-code path that is a directory or non-UTF-8/binary file now returns a 4xx (API `/simulate/from-file`) or a clean usage error (CLI `simulate`/`plot`/`stream` reject directories) instead of an HTTP 500 / raw `IsADirectoryError`/`UnicodeDecodeError` traceback.
22
+ - Loading a wind definition that is a directory, an unreadable file, or non-UTF-8/binary content now reports a clean error instead of crashing. Previously these raised an unmapped `IsADirectoryError`/`UnicodeDecodeError`, surfacing as an HTTP 500 from the API (`/plan/from-file`, `/validate/from-file`) or a raw traceback from the CLI; they are now mapped to the standard `WindFileError` (4xx / clean CLI message).
23
+ - `fiberpath plan --output <dir>/<file>.gcode` into a directory that does not exist now creates the parent directories instead of crashing with a `FileNotFoundError` traceback (matching the existing `plot` behavior). `write_gcode` creates the destination's parent directory.
24
+ - A helical layer whose `leadInMM` is greater than or equal to the mandrel `windLength` is now rejected during planning. Previously it passed validation and generated G-code that drove the carriage off the end of the mandrel into negative coordinates (inverting the main-pass rotation), risking an end-stop collision.
25
+ - Non-finite numeric inputs (`NaN`, `Infinity`) in a `.wind` file are now rejected at load instead of being silently accepted. Previously a `NaN` value could be emitted into generated G-code as a literal `Anan` axis word, and an `Infinity` geometry value crashed the planner with an uncaught `OverflowError` (HTTP 500 / CLI traceback).
26
+ - Helical layer editor no longer crashes the app ("Maximum call stack size exceeded") when the Pattern Number or Skip Index field is emptied. The emptied field became `NaN`, and the cross-field coprime check recursed forever in `gcd()`; the check now skips non-integer values and the field reports its own validation error instead.
27
+
11
28
  ## [0.7.3] - 2026-06-25
12
29
 
13
30
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fiberpath
3
- Version: 0.7.3
3
+ Version: 0.7.4
4
4
  Summary: Modern filament winding planner, simulator, and G-code toolkit.
5
5
  Author: Cameron K. Brooks
6
6
  License: GNU AFFERO GENERAL PUBLIC LICENSE
@@ -705,7 +705,7 @@ Description-Content-Type: text/markdown
705
705
 
706
706
  **Plan, simulate, and manufacture composite parts with precision fiber winding.**
707
707
 
708
- [![Version](https://img.shields.io/badge/version-0.7.3-4c7284)](https://github.com/fiberpath/fiberpath/releases)
708
+ [![Version](https://img.shields.io/badge/version-0.7.4-4c7284)](https://github.com/fiberpath/fiberpath/releases)
709
709
  [![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-00695C.svg)](LICENSE)
710
710
  [![Backend CI](https://img.shields.io/github/actions/workflow/status/fiberpath/fiberpath/backend-ci.yml?branch=main&label=Backend%20CI&logo=python&logoColor=white)](https://github.com/fiberpath/fiberpath/actions/workflows/backend-ci.yml)
711
711
  [![GUI CI](https://img.shields.io/github/actions/workflow/status/fiberpath/fiberpath/gui-ci.yml?branch=main&label=GUI%20CI&logo=react&logoColor=white)](https://github.com/fiberpath/fiberpath/actions/workflows/gui-ci.yml)
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Plan, simulate, and manufacture composite parts with precision fiber winding.**
6
6
 
7
- [![Version](https://img.shields.io/badge/version-0.7.3-4c7284)](https://github.com/fiberpath/fiberpath/releases)
7
+ [![Version](https://img.shields.io/badge/version-0.7.4-4c7284)](https://github.com/fiberpath/fiberpath/releases)
8
8
  [![License: AGPL-3.0](https://img.shields.io/badge/License-AGPL--3.0-00695C.svg)](LICENSE)
9
9
  [![Backend CI](https://img.shields.io/github/actions/workflow/status/fiberpath/fiberpath/backend-ci.yml?branch=main&label=Backend%20CI&logo=python&logoColor=white)](https://github.com/fiberpath/fiberpath/actions/workflows/backend-ci.yml)
10
10
  [![GUI CI](https://img.shields.io/github/actions/workflow/status/fiberpath/fiberpath/gui-ci.yml?branch=main&label=GUI%20CI&logo=react&logoColor=white)](https://github.com/fiberpath/fiberpath/actions/workflows/gui-ci.yml)
@@ -36,7 +36,7 @@ hide:
36
36
 
37
37
  ---
38
38
 
39
- **Latest Release:** [v0.7.3](https://github.com/fiberpath/fiberpath/releases/latest){ target=_blank }
39
+ **Latest Release:** [v0.7.4](https://github.com/fiberpath/fiberpath/releases/latest){ target=_blank }
40
40
 
41
41
  - **Desktop GUI** – Windows, macOS, Linux installers (no Python required)
42
42
  - **Python Package** – `pip install fiberpath`
@@ -44,16 +44,16 @@ hide:
44
44
 
45
45
  [:octicons-arrow-right-24: Installation Guide](getting-started.md)
46
46
 
47
- - :material-new-box:{ .lg .middle } **What's New in v0.7.3**
47
+ - :material-new-box:{ .lg .middle } **What's New in v0.7.4**
48
48
 
49
49
  ---
50
50
 
51
- Project moved to the [`fiberpath` GitHub organization](https://github.com/fiberpath):
51
+ Bug-fix release hardening input validation and the desktop integration:
52
52
 
53
- - Repository, issue tracker, and PyPI trusted publisher now live under `fiberpath/fiberpath` (`pip install fiberpath` is unchanged)
54
- - Documentation moved to [fiberpath.org](https://fiberpath.org), built from the `fiberpath.github.io` hub; this repo is now docs-content-only
55
- - Dependency automation switched from Dependabot to org-shared Renovate
56
- - `CI Check` now runs on every PR so the required status never stalls
53
+ - Reject non-finite (`NaN`/`Infinity`) inputs and a lead-in longer than the mandrel (which produced unsafe negative-coordinate G-code)
54
+ - Cleaner errors (4xx, not 500/tracebacks) for directory/binary file inputs across the API and CLI
55
+ - Marlin desktop commands no longer hang if the helper process dies, and recover by respawning
56
+ - Helical editor validation now matches the planner's bounds; fixed a crash and a "(NaN)" hint while editing
57
57
 
58
58
  Builds on the v0.7 baseline; no runtime API changes.
59
59
 
@@ -11,7 +11,15 @@ from pydantic import BaseModel, ConfigDict, Field, PositiveFloat, PositiveInt
11
11
  class BaseFiberPathModel(BaseModel):
12
12
  """Base class that applies shared Pydantic configuration."""
13
13
 
14
- model_config = ConfigDict(populate_by_name=True, str_strip_whitespace=True)
14
+ # allow_inf_nan=False rejects NaN/Infinity on every float field. JSON
15
+ # permits the NaN/Infinity literals, and unguarded non-finite values
16
+ # otherwise propagate into the generated G-code (a literal "Anan" axis word)
17
+ # or overflow math.ceil in the planner (uncaught OverflowError).
18
+ model_config = ConfigDict(
19
+ populate_by_name=True,
20
+ str_strip_whitespace=True,
21
+ allow_inf_nan=False,
22
+ )
15
23
 
16
24
 
17
25
  class MandrelParameters(BaseFiberPathModel):
@@ -21,8 +21,15 @@ def load_wind_definition(path: str | Path) -> WindDefinition:
21
21
  if not location.exists():
22
22
  raise WindFileError(f"No wind definition found at {location}")
23
23
  try:
24
- payload = json.loads(location.read_text(encoding="utf-8"))
25
- except json.JSONDecodeError as exc: # pragma: no cover - extremely rare
24
+ raw = location.read_text(encoding="utf-8")
25
+ except (OSError, UnicodeDecodeError) as exc:
26
+ # Directory, unreadable file, or non-UTF-8/binary content. Surface as a
27
+ # WindFileError so callers map it to a 4xx / clean CLI error instead of
28
+ # leaking a raw IsADirectoryError / UnicodeDecodeError (HTTP 500).
29
+ raise WindFileError(f"Could not read wind definition at {location}: {exc}") from exc
30
+ try:
31
+ payload = json.loads(raw)
32
+ except json.JSONDecodeError as exc:
26
33
  raise WindFileError(f"Invalid JSON in {location}: {exc}") from exc
27
34
 
28
35
  try:
@@ -22,5 +22,6 @@ def sanitize_program(commands: Iterable[str]) -> list[str]:
22
22
  def write_gcode(program: GCodeProgram | Sequence[str], destination: str | Path) -> Path:
23
23
  target = Path(destination)
24
24
  lines = program.commands if isinstance(program, GCodeProgram) else list(program)
25
+ target.parent.mkdir(parents=True, exist_ok=True)
25
26
  target.write_text("\n".join(lines) + "\n", encoding="utf-8")
26
27
  return target
@@ -77,6 +77,16 @@ def validate_helical_layer(
77
77
  "skipIndex and patternNumber must be coprime for full coverage",
78
78
  )
79
79
 
80
+ if layer.lead_in_mm >= mandrel.wind_length:
81
+ raise LayerValidationError(
82
+ layer_index,
83
+ (
84
+ f"leadInMM ({layer.lead_in_mm}mm) must be less than the mandrel "
85
+ f"windLength ({mandrel.wind_length}mm); a longer lead-in drives the "
86
+ "carriage off the mandrel into negative coordinates"
87
+ ),
88
+ )
89
+
80
90
  kinematics = compute_helical_kinematics(layer, mandrel, tow)
81
91
  if kinematics.num_circuits % layer.pattern_number != 0:
82
92
  raise LayerValidationError(
@@ -16,7 +16,14 @@ def simulate_from_file(payload: FilePathRequest) -> SimulationResponse:
16
16
  target = enforce_input_path_policy(payload.path)
17
17
  if not target.exists():
18
18
  raise HTTPException(status_code=404, detail=f"No file found at {payload.path}")
19
- commands = target.read_text(encoding="utf-8").splitlines()
19
+ if not target.is_file():
20
+ raise HTTPException(status_code=400, detail=f"Not a file: {payload.path}")
21
+ try:
22
+ commands = target.read_text(encoding="utf-8").splitlines()
23
+ except (OSError, UnicodeDecodeError) as exc:
24
+ raise HTTPException(
25
+ status_code=400, detail=f"Could not read {payload.path}: {exc}"
26
+ ) from exc
20
27
  result = simulate_program(commands)
21
28
  return SimulationResponse(
22
29
  commands=result.commands_executed,
@@ -35,6 +35,12 @@ def start_stream(payload: StreamRequest) -> StreamResponse:
35
35
  raise HTTPException(status_code=400, detail="port is required when dry_run is false")
36
36
 
37
37
  commands = payload.gcode.splitlines()
38
+ if not any(line.strip() for line in commands):
39
+ # Empty/whitespace-only input is a client error, not an upstream device
40
+ # failure — return 400 rather than letting the streamer raise a
41
+ # StreamError that maps to 502.
42
+ raise HTTPException(status_code=400, detail="gcode contained no commands")
43
+
38
44
  streamer = MarlinStreamer(port=payload.port, baud_rate=payload.baud_rate)
39
45
 
40
46
  try:
@@ -10,7 +10,9 @@ from rich.console import Console
10
10
 
11
11
  console = Console()
12
12
 
13
- GCODE_ARGUMENT = typer.Argument(..., exists=True, readable=True, help="Input G-code file")
13
+ GCODE_ARGUMENT = typer.Argument(
14
+ ..., exists=True, readable=True, file_okay=True, dir_okay=False, help="Input G-code file"
15
+ )
14
16
  OUTPUT_OPTION = typer.Option(Path("plot.png"), "--output", "-o", help="PNG destination")
15
17
  SCALE_OPTION = typer.Option(
16
18
  1.0, "--scale", help="Pixels per millimeter along carriage axis", min=0.1, max=5.0
@@ -10,7 +10,7 @@ from fiberpath.simulation import SimulationError, simulate_program
10
10
 
11
11
  from .output import echo_json
12
12
 
13
- GCODE_ARGUMENT = typer.Argument(..., exists=True, readable=True)
13
+ GCODE_ARGUMENT = typer.Argument(..., exists=True, readable=True, file_okay=True, dir_okay=False)
14
14
  JSON_OPTION = typer.Option(False, "--json", help="Emit machine-readable JSON summary")
15
15
 
16
16
 
@@ -9,7 +9,7 @@ from fiberpath.execution import MarlinStreamer, StreamError, StreamProgress
9
9
 
10
10
  from .output import echo_json
11
11
 
12
- GCODE_ARGUMENT = typer.Argument(..., exists=True, readable=True)
12
+ GCODE_ARGUMENT = typer.Argument(..., exists=True, readable=True, file_okay=True, dir_okay=False)
13
13
  PROGRESS_INTERVAL = 25
14
14
 
15
15
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "fiberpath-gui",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "fiberpath-gui",
9
- "version": "0.7.3",
9
+ "version": "0.7.4",
10
10
  "dependencies": {
11
11
  "@hello-pangea/dnd": "^18.0.1",
12
12
  "@tauri-apps/api": "^2.11.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fiberpath-gui",
3
- "version": "0.7.3",
3
+ "version": "0.7.4",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "engines": {
@@ -19,6 +19,7 @@
19
19
  "format:check": "cd src-tauri && cargo fmt --check",
20
20
  "format:fix": "cd src-tauri && cargo fmt",
21
21
  "clippy": "cd src-tauri && cargo clippy -- -D warnings",
22
+ "test:rust": "cd src-tauri && cargo test",
22
23
  "check:all": "npm run lint && npm run lint:css && npm run lint:css:vars && npm run format:check && npm run clippy",
23
24
  "test": "vitest",
24
25
  "test:ui": "vitest --ui",
@@ -29,6 +29,14 @@ describe("helicalValidation", () => {
29
29
  validateHelicalField("wind_angle", 91, defaultHelical),
30
30
  ).toBeDefined();
31
31
  });
32
+
33
+ it("rejects 90 and sub-1 angles, accepts the [1, 89] bounds", () => {
34
+ // The planner enforces [1, 89] (90° -> cos = 0). The editor must match.
35
+ expect(validateHelicalField("wind_angle", 90, defaultHelical)).toBeDefined();
36
+ expect(validateHelicalField("wind_angle", 0.5, defaultHelical)).toBeDefined();
37
+ expect(validateHelicalField("wind_angle", 1, defaultHelical)).toBeUndefined();
38
+ expect(validateHelicalField("wind_angle", 89, defaultHelical)).toBeUndefined();
39
+ });
32
40
  });
33
41
 
34
42
  describe("pattern_number", () => {
@@ -75,11 +83,49 @@ describe("helicalValidation", () => {
75
83
  });
76
84
  });
77
85
 
86
+ // Regression: emptying a numeric input yields NaN. Re-validating the
87
+ // sibling field then ran the coprime check (gcd) against the NaN value,
88
+ // which recursed forever and crashed the app with
89
+ // "RangeError: Maximum call stack size exceeded".
90
+ describe("does not crash when a pattern/skip field is mid-edit", () => {
91
+ it("returns a range error (not a stack overflow) for a NaN skip index", () => {
92
+ expect(() =>
93
+ validateHelicalField("skip_index", NaN, defaultHelical),
94
+ ).not.toThrow();
95
+ expect(validateHelicalField("skip_index", NaN, defaultHelical)).toBeDefined();
96
+ });
97
+
98
+ it("does not crash validating pattern_number when skip_index is NaN", () => {
99
+ const layer: HelicalLayer = { ...defaultHelical, skip_index: NaN };
100
+ expect(() =>
101
+ validateHelicalField("pattern_number", 3, layer),
102
+ ).not.toThrow();
103
+ // pattern itself is valid; the coprime check is skipped while the
104
+ // sibling is invalid, so no spurious error is reported here.
105
+ expect(validateHelicalField("pattern_number", 3, layer)).toBeUndefined();
106
+ });
107
+
108
+ it("does not crash validating skip_index when pattern_number is NaN", () => {
109
+ const layer: HelicalLayer = { ...defaultHelical, pattern_number: NaN };
110
+ expect(() =>
111
+ validateHelicalField("skip_index", 2, layer),
112
+ ).not.toThrow();
113
+ expect(validateHelicalField("skip_index", 2, layer)).toBeUndefined();
114
+ });
115
+
116
+ it("does not crash on non-integer pattern/skip values", () => {
117
+ const layer: HelicalLayer = { ...defaultHelical, skip_index: 2.5 };
118
+ expect(() =>
119
+ validateHelicalField("pattern_number", 3, layer),
120
+ ).not.toThrow();
121
+ });
122
+ });
123
+
78
124
  describe("lock_degrees", () => {
79
- it("accepts zero", () => {
125
+ it("rejects zero (planner requires a positive value)", () => {
80
126
  expect(
81
127
  validateHelicalField("lock_degrees", 0, defaultHelical),
82
- ).toBeUndefined();
128
+ ).toBeDefined();
83
129
  });
84
130
 
85
131
  it("accepts positive value", () => {
@@ -96,9 +142,15 @@ describe("helicalValidation", () => {
96
142
  });
97
143
 
98
144
  describe("lead_out_degrees", () => {
99
- it("accepts zero", () => {
145
+ it("rejects zero (planner requires a positive value)", () => {
100
146
  expect(
101
147
  validateHelicalField("lead_out_degrees", 0, defaultHelical),
148
+ ).toBeDefined();
149
+ });
150
+
151
+ it("accepts positive value", () => {
152
+ expect(
153
+ validateHelicalField("lead_out_degrees", 30, defaultHelical),
102
154
  ).toBeUndefined();
103
155
  });
104
156
 
@@ -110,9 +162,15 @@ describe("helicalValidation", () => {
110
162
  });
111
163
 
112
164
  describe("lead_in_mm", () => {
113
- it("accepts zero", () => {
165
+ it("rejects zero (planner requires a positive value)", () => {
114
166
  expect(
115
167
  validateHelicalField("lead_in_mm", 0, defaultHelical),
168
+ ).toBeDefined();
169
+ });
170
+
171
+ it("accepts positive value", () => {
172
+ expect(
173
+ validateHelicalField("lead_in_mm", 10, defaultHelical),
116
174
  ).toBeUndefined();
117
175
  });
118
176
 
@@ -134,6 +192,14 @@ describe("helicalValidation", () => {
134
192
  expect(getHelicalGeometryHint(layer, 150, 12.7)).toMatch(/skip index/i);
135
193
  });
136
194
 
195
+ it("does not emit a '(NaN)' hint while pattern_number is mid-edit", () => {
196
+ // Emptying the Pattern Number input makes it NaN; circuitCount % NaN is
197
+ // NaN (!== 0), which previously rendered "not divisible by pattern
198
+ // number (NaN)". A non-integer pattern means the hint is undefined.
199
+ const layer: HelicalLayer = { ...defaultHelical, pattern_number: NaN };
200
+ expect(getHelicalGeometryHint(layer, 150, 50)).toBeUndefined();
201
+ });
202
+
137
203
  it("returns undefined when mandrel diameter is zero", () => {
138
204
  expect(
139
205
  getHelicalGeometryHint(defaultHelical, 0, 12.7),
@@ -16,6 +16,15 @@ const gcd = (a: number, b: number): number => {
16
16
  };
17
17
 
18
18
  const validateCoprime = (pattern: number, skip: number): string | undefined => {
19
+ // Coprimality is only defined for two positive integers. While a sibling
20
+ // field is mid-edit it can be NaN (an emptied input) or a non-integer; in
21
+ // that case skip the cross-field check — the offending field reports its own
22
+ // range error. This guard also prevents gcd() from infinite-recursing on
23
+ // NaN (Math.abs(NaN) is NaN, NaN === 0 is false, and NaN % NaN is NaN, so it
24
+ // never terminates), which previously crashed the app with a stack overflow.
25
+ if (!Number.isInteger(pattern) || !Number.isInteger(skip)) {
26
+ return undefined;
27
+ }
19
28
  if (gcd(pattern, skip) !== 1) {
20
29
  return "Pattern and skip must be coprime (GCD = 1)";
21
30
  }
@@ -79,12 +88,14 @@ export function validateHelicalField(
79
88
 
80
89
  case "lock_degrees":
81
90
  case "lead_out_degrees":
82
- return value < 0
83
- ? `${field.replace("_", " ")} must be non-negative`
91
+ // The planner requires these to be strictly positive (PositiveFloat);
92
+ // accepting 0 here only defers a 422 from the backend.
93
+ return value <= 0
94
+ ? `${field.replace("_", " ")} must be positive`
84
95
  : undefined;
85
96
 
86
97
  case "lead_in_mm":
87
- return value < 0 ? "Lead-in must be non-negative" : undefined;
98
+ return value <= 0 ? "Lead-in must be positive" : undefined;
88
99
  }
89
100
  }
90
101
 
@@ -97,6 +108,13 @@ export function getHelicalGeometryHint(
97
108
  return "Skip index should be less than pattern number to avoid invalid routing.";
98
109
  }
99
110
 
111
+ // While the field is mid-edit pattern_number can be NaN/0/non-integer; the
112
+ // divisibility check below would compute `% NaN` and render a "(NaN)" hint.
113
+ // The field surfaces its own validation error, so emit no geometry hint here.
114
+ if (!Number.isInteger(helical.pattern_number) || helical.pattern_number <= 0) {
115
+ return undefined;
116
+ }
117
+
100
118
  const circuitCount = computeCircuitCount(helical, mandrelDiameter, towWidth);
101
119
  if (!circuitCount) {
102
120
  return undefined;
@@ -105,12 +105,15 @@ export const WindHoopLayerSchema = z.object({
105
105
  */
106
106
  export const WindHelicalLayerSchema = z.object({
107
107
  windType: z.literal("helical"),
108
- windAngle: z.number().min(0).max(90),
108
+ // Bounds match the planner contract (PositiveFloat/PositiveInt; wind angle
109
+ // is enforced to [1, 89]). Keeping these in sync avoids the save gate
110
+ // accepting values the engine later rejects with a 422.
111
+ windAngle: z.number().min(1).max(89),
109
112
  patternNumber: z.number().int().positive(),
110
- skipIndex: z.number().int().nonnegative(),
111
- lockDegrees: z.number().nonnegative(),
112
- leadInMM: z.number().nonnegative(),
113
- leadOutDegrees: z.number().nonnegative(),
113
+ skipIndex: z.number().int().positive(),
114
+ lockDegrees: z.number().positive(),
115
+ leadInMM: z.number().positive(),
116
+ leadOutDegrees: z.number().positive(),
114
117
  skipInitialNearLock: z.boolean().optional(),
115
118
  });
116
119
 
@@ -81,11 +81,12 @@ export interface NumericRange {
81
81
  * These constants ensure consistency in validation across components.
82
82
  */
83
83
  export const NUMERIC_RANGES = {
84
- /** Wind angle: 0° to 90° (exclusive) */
84
+ /** Wind angle: 1° to 89° (inclusive), matching the planner's enforced bounds
85
+ * (90° gives cos = 0; the engine clamps to [1, 89]). */
85
86
  WIND_ANGLE: {
86
- min: 0,
87
- max: 90,
88
- inclusive: { min: false, max: false },
87
+ min: 1,
88
+ max: 89,
89
+ inclusive: { min: true, max: true },
89
90
  } as NumericRange,
90
91
 
91
92
  /** Feed rate: 1 to 10000 mm/min */
@@ -782,7 +782,7 @@ dependencies = [
782
782
 
783
783
  [[package]]
784
784
  name = "fiberpath_gui"
785
- version = "0.7.3"
785
+ version = "0.7.4"
786
786
  dependencies = [
787
787
  "base64 0.22.1",
788
788
  "log",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fiberpath_gui"
3
- version = "0.7.3"
3
+ version = "0.7.4"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -9,6 +9,7 @@ use marlin::MarlinState;
9
9
  use serde_json::Value;
10
10
  use std::fs;
11
11
  use std::process::Output;
12
+ use std::sync::atomic::{AtomicU64, Ordering};
12
13
  use std::sync::{Arc, Mutex};
13
14
  use std::time::{SystemTime, UNIX_EPOCH};
14
15
  use tauri::{AppHandle, Manager};
@@ -208,13 +209,23 @@ async fn plot_definition(
208
209
  })
209
210
  }
210
211
 
212
+ /// Monotonic counter ensuring temp file names are unique within the process
213
+ /// even when several are created in the same millisecond.
214
+ static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);
215
+
211
216
  fn temp_path(extension: &str) -> String {
212
217
  let mut path = std::env::temp_dir();
213
218
  let millis = SystemTime::now()
214
219
  .duration_since(UNIX_EPOCH)
215
220
  .unwrap_or_default()
216
221
  .as_millis();
217
- path.push(format!("fiberpath-{millis}.{extension}"));
222
+ // A millisecond-only name collides when rapid commands (e.g. validate /
223
+ // preview firing on edits) create temp files in the same millisecond — one
224
+ // call's TempFile::Drop then deletes another's file. The PID plus a
225
+ // monotonic counter make every name unique within and across processes.
226
+ let pid = std::process::id();
227
+ let seq = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed);
228
+ path.push(format!("fiberpath-{pid}-{millis}-{seq}.{extension}"));
218
229
  path.to_string_lossy().into_owned()
219
230
  }
220
231
 
@@ -520,10 +531,20 @@ fn main() {
520
531
 
521
532
  #[cfg(test)]
522
533
  mod tests {
523
- use super::TempFile;
534
+ use super::{temp_path, TempFile};
535
+ use std::collections::HashSet;
524
536
  use std::fs;
525
537
  use std::path::Path;
526
538
 
539
+ // Regression: a millisecond-only temp name collided when many temp files
540
+ // were created in the same millisecond, so one call's TempFile::Drop could
541
+ // delete another's file. Rapidly generated paths must all be unique.
542
+ #[test]
543
+ fn temp_path_is_unique_under_rapid_calls() {
544
+ let paths: HashSet<String> = (0..1000).map(|_| temp_path("gcode")).collect();
545
+ assert_eq!(paths.len(), 1000, "temp_path produced duplicate names");
546
+ }
547
+
527
548
  #[test]
528
549
  fn temp_file_is_removed_on_drop() {
529
550
  let path = {