From cef5eafa9febbd018e2dab516b9394fb6cfa08cd Mon Sep 17 00:00:00 2001 From: matei jordache Date: Tue, 31 Mar 2026 22:16:52 -0700 Subject: [PATCH] initial migration to TS --- frontend/eslint.config.js | 10 +- frontend/index.html | 2 +- frontend/package-lock.json | 452 ++++++++++++++++++ frontend/package.json | 5 + ...ureOverlay.jsx => AngleMeasureOverlay.tsx} | 2 +- frontend/src/{App.jsx => App.tsx} | 10 +- ...{CropBoxOverlay.jsx => CropBoxOverlay.tsx} | 0 ...ionOverlay.jsx => CrossSectionOverlay.tsx} | 0 .../src/{CustomNode.jsx => CustomNode.tsx} | 6 +- ...pPanelManager.jsx => HelpPanelManager.tsx} | 0 ...inePlotOverlay.jsx => LinePlotOverlay.tsx} | 0 .../{MarkupOverlay.jsx => MarkupOverlay.tsx} | 2 +- ...kPaintOverlay.jsx => MaskPaintOverlay.tsx} | 0 .../src/{SurfaceView.jsx => SurfaceView.tsx} | 0 .../{TextNoteNode.jsx => TextNoteNode.tsx} | 0 ...ldHistogram.jsx => ThresholdHistogram.tsx} | 0 ...ureGeometry.js => angleMeasureGeometry.ts} | 0 frontend/src/{api.js => api.ts} | 0 ...Targets.js => canvasInteractionTargets.ts} | 21 +- ...{connectionUtils.js => connectionUtils.ts} | 2 +- frontend/src/{constants.js => constants.ts} | 26 +- ...{defaultWorkflow.js => defaultWorkflow.ts} | 2 +- .../{executionGraph.js => executionGraph.ts} | 2 +- frontend/src/{groupDrag.js => groupDrag.ts} | 16 +- .../src/{groupSizing.js => groupSizing.ts} | 12 +- ...{loadNodeOutputs.js => loadNodeOutputs.ts} | 12 +- frontend/src/{main.jsx => main.tsx} | 0 ...hapeGeometry.js => markupShapeGeometry.ts} | 0 .../src/{nativePicker.js => nativePicker.ts} | 0 .../{nodeClipboard.js => nodeClipboard.ts} | 2 +- .../{nodeHierarchy.js => nodeHierarchy.ts} | 16 +- ...idgetDefaults.js => nodeWidgetDefaults.ts} | 2 +- ...odeWidgetLayout.js => nodeWidgetLayout.ts} | 4 +- .../src/{pngMetadata.js => pngMetadata.ts} | 0 ...sistence.js => runtimeValuePersistence.ts} | 2 +- frontend/src/types.ts | 235 +++++++++ .../src/{useUndoRedo.js => useUndoRedo.ts} | 0 ...{valueFormatting.js => valueFormatting.ts} | 0 ...{workflowCapture.js => workflowCapture.ts} | 2 +- ...kflowHydration.js => workflowHydration.ts} | 4 +- ...{workflowPacking.js => workflowPacking.ts} | 2 +- ...ialization.js => workflowSerialization.ts} | 2 +- frontend/tests/angleMeasureGeometry.test.mjs | 2 +- .../tests/canvasInteractionTargets.test.mjs | 2 +- frontend/tests/connectionUtils.test.mjs | 2 +- frontend/tests/constants.test.mjs | 2 +- frontend/tests/defaultWorkflow.test.mjs | 4 +- frontend/tests/executionGraph.test.mjs | 2 +- frontend/tests/groupDrag.test.mjs | 2 +- frontend/tests/groupSizing.test.mjs | 2 +- frontend/tests/loadNodeOutputs.test.mjs | 2 +- frontend/tests/markupShapeGeometry.test.mjs | 2 +- frontend/tests/nodeClipboard.test.mjs | 2 +- frontend/tests/nodeHierarchy.test.mjs | 6 +- frontend/tests/nodeWidgetDefaults.test.mjs | 2 +- frontend/tests/nodeWidgetLayout.test.mjs | 2 +- frontend/tests/pngMetadata.test.mjs | 2 +- frontend/tests/valueFormatting.test.mjs | 2 +- frontend/tests/workflowCapture.test.mjs | 2 +- frontend/tests/workflowSerialization.test.mjs | 4 +- frontend/tsconfig.json | 19 + 61 files changed, 831 insertions(+), 85 deletions(-) rename frontend/src/{AngleMeasureOverlay.jsx => AngleMeasureOverlay.tsx} (99%) rename frontend/src/{App.jsx => App.tsx} (99%) rename frontend/src/{CropBoxOverlay.jsx => CropBoxOverlay.tsx} (100%) rename frontend/src/{CrossSectionOverlay.jsx => CrossSectionOverlay.tsx} (100%) rename frontend/src/{CustomNode.jsx => CustomNode.tsx} (99%) rename frontend/src/{HelpPanelManager.jsx => HelpPanelManager.tsx} (100%) rename frontend/src/{LinePlotOverlay.jsx => LinePlotOverlay.tsx} (100%) rename frontend/src/{MarkupOverlay.jsx => MarkupOverlay.tsx} (99%) rename frontend/src/{MaskPaintOverlay.jsx => MaskPaintOverlay.tsx} (100%) rename frontend/src/{SurfaceView.jsx => SurfaceView.tsx} (100%) rename frontend/src/{TextNoteNode.jsx => TextNoteNode.tsx} (100%) rename frontend/src/{ThresholdHistogram.jsx => ThresholdHistogram.tsx} (100%) rename frontend/src/{angleMeasureGeometry.js => angleMeasureGeometry.ts} (100%) rename frontend/src/{api.js => api.ts} (100%) rename frontend/src/{canvasInteractionTargets.js => canvasInteractionTargets.ts} (63%) rename frontend/src/{connectionUtils.js => connectionUtils.ts} (98%) rename frontend/src/{constants.js => constants.ts} (74%) rename frontend/src/{defaultWorkflow.js => defaultWorkflow.ts} (96%) rename frontend/src/{executionGraph.js => executionGraph.ts} (99%) rename frontend/src/{groupDrag.js => groupDrag.ts} (53%) rename frontend/src/{groupSizing.js => groupSizing.ts} (73%) rename frontend/src/{loadNodeOutputs.js => loadNodeOutputs.ts} (61%) rename frontend/src/{main.jsx => main.tsx} (100%) rename frontend/src/{markupShapeGeometry.js => markupShapeGeometry.ts} (100%) rename frontend/src/{nativePicker.js => nativePicker.ts} (100%) rename frontend/src/{nodeClipboard.js => nodeClipboard.ts} (99%) rename frontend/src/{nodeHierarchy.js => nodeHierarchy.ts} (63%) rename frontend/src/{nodeWidgetDefaults.js => nodeWidgetDefaults.ts} (98%) rename frontend/src/{nodeWidgetLayout.js => nodeWidgetLayout.ts} (92%) rename frontend/src/{pngMetadata.js => pngMetadata.ts} (100%) rename frontend/src/{runtimeValuePersistence.js => runtimeValuePersistence.ts} (54%) create mode 100644 frontend/src/types.ts rename frontend/src/{useUndoRedo.js => useUndoRedo.ts} (100%) rename frontend/src/{valueFormatting.js => valueFormatting.ts} (100%) rename frontend/src/{workflowCapture.js => workflowCapture.ts} (99%) rename frontend/src/{workflowHydration.js => workflowHydration.ts} (96%) rename frontend/src/{workflowPacking.js => workflowPacking.ts} (99%) rename frontend/src/{workflowSerialization.js => workflowSerialization.ts} (99%) create mode 100644 frontend/tsconfig.json diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index b33999c..1f650bd 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -1,11 +1,13 @@ import js from '@eslint/js'; import react from 'eslint-plugin-react'; import reactHooks from 'eslint-plugin-react-hooks'; +import tseslint from 'typescript-eslint'; export default [ js.configs.recommended, + ...tseslint.configs.recommended, { - files: ['src/**/*.{js,jsx}'], + files: ['src/**/*.{js,jsx,ts,tsx}'], plugins: { react, 'react-hooks': reactHooks, @@ -13,6 +15,7 @@ export default [ languageOptions: { ecmaVersion: 'latest', sourceType: 'module', + parser: tseslint.parser, parserOptions: { ecmaFeatures: { jsx: true } }, globals: { // Browser APIs @@ -77,7 +80,10 @@ export default [ 'react/react-in-jsx-scope': 'off', // not needed with React 17+ JSX transform 'react/prop-types': 'off', // no PropTypes in this codebase 'react/no-unknown-property': 'off', // false positives with Three.js / custom attrs - 'no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-require-imports': 'off', 'no-empty': 'off', 'no-prototype-builtins': 'off', }, diff --git a/frontend/index.html b/frontend/index.html index 91f66fe..b48dbe1 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -7,6 +7,6 @@
- + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 56f2b09..a3b4b6e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,11 +15,16 @@ }, "devDependencies": { "@eslint/js": "^9.39.4", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@types/three": "^0.183.1", "@vitejs/plugin-react": "^4.3.0", "c8": "^10.1.3", "eslint": "^9.39.4", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", + "typescript": "^6.0.2", + "typescript-eslint": "^8.58.0", "vite": "^5.4.0" }, "engines": { @@ -319,6 +324,13 @@ "node": ">=18" } }, + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", + "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -1414,6 +1426,13 @@ "win32" ] }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1529,6 +1548,312 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.183.1", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.183.1.tgz", + "integrity": "sha512-f2Pu5Hrepfgavttdye3PsH5RWyY/AvdZQwIVhrc4uNtvF7nOWJacQKcoVJn0S4f0yYbmAE6AR+ve7xDcuYtMGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@dimforge/rapier3d-compat": "~0.12.0", + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": ">=0.5.17", + "@webgpu/types": "*", + "fflate": "~0.8.2", + "meshoptimizer": "~1.0.1" + } + }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", + "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/type-utils": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.58.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz", + "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", + "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.0", + "@typescript-eslint/types": "^8.58.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", + "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", + "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", + "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", + "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", + "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.58.0", + "@typescript-eslint/tsconfig-utils": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", + "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", + "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -1550,6 +1875,13 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@webgpu/types": { + "version": "0.1.69", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz", + "integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/@xyflow/react": { "version": "12.10.1", "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.1.tgz", @@ -2186,6 +2518,13 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", @@ -2978,6 +3317,31 @@ "dev": true, "license": "MIT" }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -4156,6 +4520,13 @@ "node": ">= 0.4" } }, + "node_modules/meshoptimizer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.0.1.tgz", + "integrity": "sha512-Vix+QlA1YYT3FwmBBZ+49cE5y/b+pRrcXKqGpS5ouh33d3lSp2PoTpCw19E0cKDFWalembrHnIaZetf27a+W2g==", + "dev": true, + "license": "MIT" + }, "node_modules/minimatch": { "version": "10.2.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", @@ -4495,6 +4866,19 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -5262,6 +5646,36 @@ "integrity": "sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==", "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5353,6 +5767,44 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz", + "integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.0", + "@typescript-eslint/parser": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 42def0f..a538527 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,11 +24,16 @@ }, "devDependencies": { "@eslint/js": "^9.39.4", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@types/three": "^0.183.1", "@vitejs/plugin-react": "^4.3.0", "c8": "^10.1.3", "eslint": "^9.39.4", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", + "typescript": "^6.0.2", + "typescript-eslint": "^8.58.0", "vite": "^5.4.0" } } diff --git a/frontend/src/AngleMeasureOverlay.jsx b/frontend/src/AngleMeasureOverlay.tsx similarity index 99% rename from frontend/src/AngleMeasureOverlay.jsx rename to frontend/src/AngleMeasureOverlay.tsx index 31bae8a..8ac9235 100644 --- a/frontend/src/AngleMeasureOverlay.jsx +++ b/frontend/src/AngleMeasureOverlay.tsx @@ -8,7 +8,7 @@ import { measureAngleDegrees, moveAngleWidget, round3, -} from './angleMeasureGeometry.js'; +} from './angleMeasureGeometry'; function clamp01(value) { return Math.max(0, Math.min(1, Number(value) || 0)); diff --git a/frontend/src/App.jsx b/frontend/src/App.tsx similarity index 99% rename from frontend/src/App.jsx rename to frontend/src/App.tsx index 3836fa8..b7d76b8 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.tsx @@ -20,7 +20,7 @@ import { hydrateWorkflowState } from './workflowHydration'; import useUndoRedo from './useUndoRedo'; import { packWorkflow, unpackWorkflow } from './workflowPacking'; import { serializeWorkflowState } from './workflowSerialization'; -import { sortNodesForParentOrder } from './nodeHierarchy.js'; +import { sortNodesForParentOrder } from './nodeHierarchy'; import { buildNodeClipboardPayload, buildNodeClipboardPayloadForIds, @@ -38,8 +38,8 @@ import { beginTrackedNodeRequest, isTrackedNodeRequestCurrent, resolveLoadNodeChannelPath, -} from './loadNodeOutputs.js'; -import { buildDefaultWidgetValues } from './nodeWidgetDefaults.js'; +} from './loadNodeOutputs'; +import { buildDefaultWidgetValues } from './nodeWidgetDefaults'; import { getHandleType, getInputName, @@ -52,7 +52,7 @@ import { outputTypeCanConnectToTarget, resolveOutputTypeForTarget, checkConnectionValid, -} from './connectionUtils.js'; +} from './connectionUtils'; import { getSpecTypeAndOptions, @@ -2624,7 +2624,7 @@ function Flow() { let nextNodes = currentNodes; let changed = false; - let structureChanged = false; + const structureChanged = false; nextNodes = nextNodes.map((candidate) => { const candidateId = String(candidate.id); diff --git a/frontend/src/CropBoxOverlay.jsx b/frontend/src/CropBoxOverlay.tsx similarity index 100% rename from frontend/src/CropBoxOverlay.jsx rename to frontend/src/CropBoxOverlay.tsx diff --git a/frontend/src/CrossSectionOverlay.jsx b/frontend/src/CrossSectionOverlay.tsx similarity index 100% rename from frontend/src/CrossSectionOverlay.jsx rename to frontend/src/CrossSectionOverlay.tsx diff --git a/frontend/src/CustomNode.jsx b/frontend/src/CustomNode.tsx similarity index 99% rename from frontend/src/CustomNode.jsx rename to frontend/src/CustomNode.tsx index 8f4f050..08a5cad 100644 --- a/frontend/src/CustomNode.jsx +++ b/frontend/src/CustomNode.tsx @@ -18,9 +18,9 @@ import TextNoteNode from './TextNoteNode'; import { getSpecTypeAndOptions, isDataSocketSpec, SOCKET_WIDGET_TYPES, TYPE_COLORS, CAT_COLORS, } from './constants'; -import { getGroupMinimumSize } from './groupSizing.js'; -import { buildCombinedInputNameByWidgetName, formatUiLabel } from './nodeWidgetLayout.js'; -import { applySIPrefix, formatNumericCell, formatTableRowCell, getTableColumns, parseNumberWithUnit } from './valueFormatting.js'; +import { getGroupMinimumSize } from './groupSizing'; +import { buildCombinedInputNameByWidgetName, formatUiLabel } from './nodeWidgetLayout'; +import { applySIPrefix, formatNumericCell, formatTableRowCell, getTableColumns, parseNumberWithUnit } from './valueFormatting'; // ── Context (provided by App) ───────────────────────────────────────── diff --git a/frontend/src/HelpPanelManager.jsx b/frontend/src/HelpPanelManager.tsx similarity index 100% rename from frontend/src/HelpPanelManager.jsx rename to frontend/src/HelpPanelManager.tsx diff --git a/frontend/src/LinePlotOverlay.jsx b/frontend/src/LinePlotOverlay.tsx similarity index 100% rename from frontend/src/LinePlotOverlay.jsx rename to frontend/src/LinePlotOverlay.tsx diff --git a/frontend/src/MarkupOverlay.jsx b/frontend/src/MarkupOverlay.tsx similarity index 99% rename from frontend/src/MarkupOverlay.jsx rename to frontend/src/MarkupOverlay.tsx index 35a5b8d..976d4bf 100644 --- a/frontend/src/MarkupOverlay.jsx +++ b/frontend/src/MarkupOverlay.tsx @@ -9,7 +9,7 @@ import { parseMarkupShapes, sanitizeMarkupColor, sanitizeMarkupShape, -} from './markupShapeGeometry.js'; +} from './markupShapeGeometry'; function clampFraction(value) { const numeric = Number(value); diff --git a/frontend/src/MaskPaintOverlay.jsx b/frontend/src/MaskPaintOverlay.tsx similarity index 100% rename from frontend/src/MaskPaintOverlay.jsx rename to frontend/src/MaskPaintOverlay.tsx diff --git a/frontend/src/SurfaceView.jsx b/frontend/src/SurfaceView.tsx similarity index 100% rename from frontend/src/SurfaceView.jsx rename to frontend/src/SurfaceView.tsx diff --git a/frontend/src/TextNoteNode.jsx b/frontend/src/TextNoteNode.tsx similarity index 100% rename from frontend/src/TextNoteNode.jsx rename to frontend/src/TextNoteNode.tsx diff --git a/frontend/src/ThresholdHistogram.jsx b/frontend/src/ThresholdHistogram.tsx similarity index 100% rename from frontend/src/ThresholdHistogram.jsx rename to frontend/src/ThresholdHistogram.tsx diff --git a/frontend/src/angleMeasureGeometry.js b/frontend/src/angleMeasureGeometry.ts similarity index 100% rename from frontend/src/angleMeasureGeometry.js rename to frontend/src/angleMeasureGeometry.ts diff --git a/frontend/src/api.js b/frontend/src/api.ts similarity index 100% rename from frontend/src/api.js rename to frontend/src/api.ts diff --git a/frontend/src/canvasInteractionTargets.js b/frontend/src/canvasInteractionTargets.ts similarity index 63% rename from frontend/src/canvasInteractionTargets.js rename to frontend/src/canvasInteractionTargets.ts index e0b8306..2af3f98 100644 --- a/frontend/src/canvasInteractionTargets.js +++ b/frontend/src/canvasInteractionTargets.ts @@ -1,31 +1,32 @@ const EXCLUDED_CANVAS_TARGETS = '.context-menu, .react-flow__node, .react-flow__edge, .react-flow__controls, .react-flow__minimap, .surface-view-container'; const CANVAS_AREA_TARGETS = '.react-flow, .react-flow__renderer, .react-flow__viewport, .react-flow__pane, .react-flow__background, .react-flow__selectionpane'; -function getTargetElement(target) { +function getTargetElement(target: EventTarget | null): Element | null { if (!target) return null; - if (typeof target.closest === 'function') return target; - if (target.parentElement && typeof target.parentElement.closest === 'function') { - return target.parentElement; + if (typeof (target as Element).closest === 'function') return target as Element; + const parent = (target as Node).parentElement; + if (parent && typeof parent.closest === 'function') { + return parent; } return null; } -function supportsClosest(target) { +function supportsClosest(target: EventTarget | null): boolean { return !!getTargetElement(target); } -function matchesClosest(target, selector) { +function matchesClosest(target: EventTarget | null, selector: string): boolean { const element = getTargetElement(target); return !!element && element.closest(selector) !== null; } -export function isEditableInteractionTarget(target) { +export function isEditableInteractionTarget(target: EventTarget | null): boolean { if (!supportsClosest(target)) return false; if (matchesClosest(target, 'input, textarea, select')) return true; return matchesClosest(target, '[contenteditable="true"]'); } -export function canStartCanvasRightDragZoomTarget(target) { +export function canStartCanvasRightDragZoomTarget(target: EventTarget | null): boolean { if (!supportsClosest(target)) return false; if (isEditableInteractionTarget(target)) return false; if (matchesClosest(target, EXCLUDED_CANVAS_TARGETS)) { @@ -34,7 +35,7 @@ export function canStartCanvasRightDragZoomTarget(target) { return matchesClosest(target, CANVAS_AREA_TARGETS); } -export function canOpenCanvasContextMenuTarget(target) { +export function canOpenCanvasContextMenuTarget(target: EventTarget | null): boolean { if (!supportsClosest(target)) return false; if (isEditableInteractionTarget(target)) return false; if (matchesClosest(target, EXCLUDED_CANVAS_TARGETS)) { @@ -43,7 +44,7 @@ export function canOpenCanvasContextMenuTarget(target) { return matchesClosest(target, CANVAS_AREA_TARGETS); } -export function isSecondaryCanvasContextEvent(event) { +export function isSecondaryCanvasContextEvent(event: MouseEvent | null): boolean { if (!event || typeof event.button !== 'number') return false; return event.button === 2 || (event.button === 0 && !!event.ctrlKey); } diff --git a/frontend/src/connectionUtils.js b/frontend/src/connectionUtils.ts similarity index 98% rename from frontend/src/connectionUtils.js rename to frontend/src/connectionUtils.ts index 20983af..2f677c9 100644 --- a/frontend/src/connectionUtils.js +++ b/frontend/src/connectionUtils.ts @@ -1,7 +1,7 @@ // ── Connection utility functions ─────────────────────────────────────── // Pure functions extracted from App.jsx so they can be independently tested. -import { socketSpecAcceptsType } from './constants.js'; +import { socketSpecAcceptsType } from './constants.ts'; // ── Handle ID helpers ───────────────────────────────────────────────── diff --git a/frontend/src/constants.js b/frontend/src/constants.ts similarity index 74% rename from frontend/src/constants.js rename to frontend/src/constants.ts index d1df73c..c55b64c 100644 --- a/frontend/src/constants.js +++ b/frontend/src/constants.ts @@ -1,3 +1,5 @@ +import type { InputSpec, InputOptions } from './types.ts'; + // ── Shared type & color constants ───────────────────────────────────── export const DATA_TYPES = new Set([ @@ -8,7 +10,7 @@ export const DATA_TYPES = new Set([ export const SOCKET_WIDGET_TYPES = new Set(['FLOAT', 'INT']); -export const TYPE_COLORS = { +export const TYPE_COLORS: Record = { DATA_FIELD: '#3a7abf', IMAGE: '#00ff08a0', LINE: '#ffbe5c', @@ -26,7 +28,7 @@ export const TYPE_COLORS = { DIRECTORY: '#f97316', }; -export const CAT_COLORS = { +export const CAT_COLORS: Record = { Input: '#37474f', Display: '#212121', Overlay: '#0f766e', @@ -39,34 +41,34 @@ export const CAT_COLORS = { Grains: '#bf360c', }; -export const SOCKET_COMPATIBILITY = { +export const SOCKET_COMPATIBILITY: Record> = { FLOAT: new Set(['INT']), INT: new Set(['FLOAT']), LINE: new Set(['COORDPAIR']), }; -const EMPTY_SOCKET_TYPE_SET = new Set(); +const EMPTY_SOCKET_TYPE_SET: Set = new Set(); -export function getSpecTypeAndOptions(spec) { +export function getSpecTypeAndOptions(spec: InputSpec): [string | string[], InputOptions] { if (Array.isArray(spec)) { - return [spec[0], spec[1] || {}]; + return [spec[0], (spec[1] || {}) as InputOptions]; } return [spec, {}]; } -export function isDataSocketType(type) { +export function isDataSocketType(type: unknown): boolean { return typeof type === 'string' && DATA_TYPES.has(type); } -export function isDataSocketSpec(spec) { +export function isDataSocketSpec(spec: InputSpec): boolean { const [type] = getSpecTypeAndOptions(spec); return isDataSocketType(type); } -export function getAcceptedSocketTypes(specOrType) { +export function getAcceptedSocketTypes(specOrType: InputSpec | string): Set { const [type, opts] = Array.isArray(specOrType) - ? getSpecTypeAndOptions(specOrType) - : [specOrType, {}]; + ? getSpecTypeAndOptions(specOrType as InputSpec) + : [specOrType, {} as InputOptions]; if (typeof type !== 'string') { return EMPTY_SOCKET_TYPE_SET; } @@ -89,7 +91,7 @@ export function getAcceptedSocketTypes(specOrType) { return accepted; } -export function socketSpecAcceptsType(sourceType, targetSpecOrType) { +export function socketSpecAcceptsType(sourceType: string, targetSpecOrType: InputSpec | string): boolean { if (typeof sourceType !== 'string' || !sourceType) return false; return getAcceptedSocketTypes(targetSpecOrType).has(sourceType); } diff --git a/frontend/src/defaultWorkflow.js b/frontend/src/defaultWorkflow.ts similarity index 96% rename from frontend/src/defaultWorkflow.js rename to frontend/src/defaultWorkflow.ts index d86b940..be16b5b 100644 --- a/frontend/src/defaultWorkflow.js +++ b/frontend/src/defaultWorkflow.ts @@ -1,4 +1,4 @@ -import { extractWorkflow } from './pngMetadata.js'; +import { extractWorkflow } from './pngMetadata.ts'; const DEFAULT_WORKFLOW_CANDIDATES = [ { path: '/default-workflow.json', type: 'json' }, diff --git a/frontend/src/executionGraph.js b/frontend/src/executionGraph.ts similarity index 99% rename from frontend/src/executionGraph.js rename to frontend/src/executionGraph.ts index dd795d6..68c0d9f 100644 --- a/frontend/src/executionGraph.js +++ b/frontend/src/executionGraph.ts @@ -1,4 +1,4 @@ -import { getSpecTypeAndOptions, isDataSocketSpec } from './constants.js'; +import { getSpecTypeAndOptions, isDataSocketSpec } from './constants.ts'; const OMITTED_WIDGET_INPUTS_BY_CLASS = { View3D: new Set([ diff --git a/frontend/src/groupDrag.js b/frontend/src/groupDrag.ts similarity index 53% rename from frontend/src/groupDrag.js rename to frontend/src/groupDrag.ts index 9851a41..7867922 100644 --- a/frontend/src/groupDrag.js +++ b/frontend/src/groupDrag.ts @@ -1,6 +1,18 @@ export const GROUP_DRAG_RELEASE_DISTANCE = 18; -export function getPointDistanceOutsideRect(rect, point) { +interface Rect { + left: number; + right: number; + top: number; + bottom: number; +} + +interface Point { + x: number; + y: number; +} + +export function getPointDistanceOutsideRect(rect: Rect | null, point: Point | null): number { if (!rect || !point) return Infinity; const dx = point.x < rect.left @@ -13,6 +25,6 @@ export function getPointDistanceOutsideRect(rect, point) { return Math.hypot(dx, dy); } -export function shouldReleaseFromGroup(rect, point, threshold = GROUP_DRAG_RELEASE_DISTANCE) { +export function shouldReleaseFromGroup(rect: Rect | null, point: Point | null, threshold = GROUP_DRAG_RELEASE_DISTANCE): boolean { return getPointDistanceOutsideRect(rect, point) >= threshold; } diff --git a/frontend/src/groupSizing.js b/frontend/src/groupSizing.ts similarity index 73% rename from frontend/src/groupSizing.js rename to frontend/src/groupSizing.ts index 0b415b9..b57b6c1 100644 --- a/frontend/src/groupSizing.js +++ b/frontend/src/groupSizing.ts @@ -1,7 +1,15 @@ const DEFAULT_CHILD_WIDTH = 200; const DEFAULT_CHILD_HEIGHT = 120; -function getNodeSize(node, axis) { +interface SizableNode { + position?: { x: number; y: number }; + measured?: { width?: number; height?: number }; + width?: number; + height?: number; + style?: Record; +} + +function getNodeSize(node: SizableNode | null | undefined, axis: 'width' | 'height'): number { const fallback = axis === 'width' ? DEFAULT_CHILD_WIDTH : DEFAULT_CHILD_HEIGHT; const measured = Number(node?.measured?.[axis]); if (Number.isFinite(measured) && measured > 0) return measured; @@ -12,7 +20,7 @@ function getNodeSize(node, axis) { return fallback; } -export function getGroupMinimumSize(memberNodes, { +export function getGroupMinimumSize(memberNodes: SizableNode[] | null | undefined, { minWidth = 260, minHeight = 180, paddingX = 24, diff --git a/frontend/src/loadNodeOutputs.js b/frontend/src/loadNodeOutputs.ts similarity index 61% rename from frontend/src/loadNodeOutputs.js rename to frontend/src/loadNodeOutputs.ts index bd50b60..cd3ea9d 100644 --- a/frontend/src/loadNodeOutputs.js +++ b/frontend/src/loadNodeOutputs.ts @@ -1,9 +1,9 @@ export function resolveLoadNodeChannelPath({ - explicitPath = null, - resolvedPathInput = null, + explicitPath = null as string | null, + resolvedPathInput = null as string | null, className = '', - widgetValues = {}, -} = {}) { + widgetValues = {} as Record, +} = {}): string { if (typeof explicitPath === 'string' && explicitPath) { return explicitPath; } @@ -19,12 +19,12 @@ export function resolveLoadNodeChannelPath({ return ''; } -export function beginTrackedNodeRequest(requestVersions, nodeId) { +export function beginTrackedNodeRequest(requestVersions: Map, nodeId: string): number { const nextVersion = (requestVersions.get(nodeId) || 0) + 1; requestVersions.set(nodeId, nextVersion); return nextVersion; } -export function isTrackedNodeRequestCurrent(requestVersions, nodeId, version) { +export function isTrackedNodeRequestCurrent(requestVersions: Map, nodeId: string, version: number): boolean { return requestVersions.get(nodeId) === version; } diff --git a/frontend/src/main.jsx b/frontend/src/main.tsx similarity index 100% rename from frontend/src/main.jsx rename to frontend/src/main.tsx diff --git a/frontend/src/markupShapeGeometry.js b/frontend/src/markupShapeGeometry.ts similarity index 100% rename from frontend/src/markupShapeGeometry.js rename to frontend/src/markupShapeGeometry.ts diff --git a/frontend/src/nativePicker.js b/frontend/src/nativePicker.ts similarity index 100% rename from frontend/src/nativePicker.js rename to frontend/src/nativePicker.ts diff --git a/frontend/src/nodeClipboard.js b/frontend/src/nodeClipboard.ts similarity index 99% rename from frontend/src/nodeClipboard.js rename to frontend/src/nodeClipboard.ts index 9f336e7..d143fcc 100644 --- a/frontend/src/nodeClipboard.js +++ b/frontend/src/nodeClipboard.ts @@ -1,4 +1,4 @@ -import { sortNodesForParentOrder } from './nodeHierarchy.js'; +import { sortNodesForParentOrder } from './nodeHierarchy.ts'; export const NODE_CLIPBOARD_KIND = 'tono/node-selection'; export const NODE_CLIPBOARD_MIME = 'application/x-tono-node-selection'; diff --git a/frontend/src/nodeHierarchy.js b/frontend/src/nodeHierarchy.ts similarity index 63% rename from frontend/src/nodeHierarchy.js rename to frontend/src/nodeHierarchy.ts index c3742c8..4eada13 100644 --- a/frontend/src/nodeHierarchy.js +++ b/frontend/src/nodeHierarchy.ts @@ -1,12 +1,18 @@ -export function sortNodesForParentOrder(nodes) { +interface NodeLike { + id: string | number; + parentId?: string | number; + [key: string]: unknown; +} + +export function sortNodesForParentOrder(nodes: T[]): T[] { const list = Array.isArray(nodes) ? nodes.filter(Boolean) : []; const entries = list.map((node) => ({ id: String(node.id), node })); const byId = new Map(entries.map((entry) => [entry.id, entry])); - const visiting = new Set(); - const visited = new Set(); - const ordered = []; + const visiting = new Set(); + const visited = new Set(); + const ordered: T[] = []; - function visit(entry) { + function visit(entry: { id: string; node: T } | undefined) { if (!entry) return; const { id, node } = entry; if (visited.has(id) || visiting.has(id)) return; diff --git a/frontend/src/nodeWidgetDefaults.js b/frontend/src/nodeWidgetDefaults.ts similarity index 98% rename from frontend/src/nodeWidgetDefaults.js rename to frontend/src/nodeWidgetDefaults.ts index c198130..b17d07f 100644 --- a/frontend/src/nodeWidgetDefaults.js +++ b/frontend/src/nodeWidgetDefaults.ts @@ -1,4 +1,4 @@ -import { getSpecTypeAndOptions, isDataSocketSpec } from './constants.js'; +import { getSpecTypeAndOptions, isDataSocketSpec } from './constants.ts'; export function getDefaultWidgetValue(spec) { const [type, opts] = getSpecTypeAndOptions(spec); diff --git a/frontend/src/nodeWidgetLayout.js b/frontend/src/nodeWidgetLayout.ts similarity index 92% rename from frontend/src/nodeWidgetLayout.js rename to frontend/src/nodeWidgetLayout.ts index 4e7a3d5..d592861 100644 --- a/frontend/src/nodeWidgetLayout.js +++ b/frontend/src/nodeWidgetLayout.ts @@ -1,4 +1,4 @@ -export function formatUiLabel(text) { +export function formatUiLabel(text: unknown): string { return String(text ?? '') .replace(/_/g, ' ') .replace(/\s+/g, ' ') @@ -6,7 +6,7 @@ export function formatUiLabel(text) { .toLowerCase(); } -function normalizeInputNames(raw) { +function normalizeInputNames(raw: unknown): string[] { if (!raw) return []; return (Array.isArray(raw) ? raw : [raw]) .map((value) => String(value)) diff --git a/frontend/src/pngMetadata.js b/frontend/src/pngMetadata.ts similarity index 100% rename from frontend/src/pngMetadata.js rename to frontend/src/pngMetadata.ts diff --git a/frontend/src/runtimeValuePersistence.js b/frontend/src/runtimeValuePersistence.ts similarity index 54% rename from frontend/src/runtimeValuePersistence.js rename to frontend/src/runtimeValuePersistence.ts index 763d146..8aefe91 100644 --- a/frontend/src/runtimeValuePersistence.js +++ b/frontend/src/runtimeValuePersistence.ts @@ -1,4 +1,4 @@ -export function sanitizeRuntimeValuesForPersistence(className, runtimeValues) { +export function sanitizeRuntimeValuesForPersistence(className: string | undefined, runtimeValues: Record | undefined): Record { if (!runtimeValues || typeof runtimeValues !== 'object' || Array.isArray(runtimeValues)) { return {}; } diff --git a/frontend/src/types.ts b/frontend/src/types.ts new file mode 100644 index 0000000..ad8f08a --- /dev/null +++ b/frontend/src/types.ts @@ -0,0 +1,235 @@ +import type { Node, Edge } from '@xyflow/react'; +import type { CSSProperties } from 'react'; + +// ── Input Specifications ───────────────────────────────────────────── + +export interface InputOptions { + label?: string; + hidden?: boolean; + socket_only?: boolean; + accepted_types?: string[]; + default?: unknown; + placeholder?: string; + min?: number; + max?: number; + step?: number; + slider?: boolean; + min_widget?: string; + max_widget?: string; + text_input?: boolean; + color_picker?: boolean; + colormap_stops?: boolean; + set_widgets?: Record; + show_when_source_type?: Record; + show_when_widget_value?: Record; + show_when_input_visible?: string | string[]; + hide_when_input_connected?: string | string[]; + choices_by_source_type?: Record; + choices_from_table_input?: string; + choices_from_measure_input?: string; + inline_with_input?: string | [string]; + source_type_input?: string; + placement?: 'top'; +} + +/** An input spec is either a bare type string, or a [type, opts] tuple. */ +export type InputSpec = string | [type: string | string[], opts?: InputOptions]; + +// ── Node Definition (from GET /nodes) ──────────────────────────────── + +export interface NodeDefinition { + input: { + required: Record; + optional: Record; + }; + output: string[]; + output_name: string[]; + output_paths?: string[]; + category: string; + manual_trigger?: boolean; +} + +export type NodeDefsRegistry = Record; + +// ── Overlay Types ──────────────────────────────────────────────────── + +export interface OverlayData { + kind: string; + image?: string; + image_width?: number; + image_height?: number; + x1?: number; + y1?: number; + x2?: number; + y2?: number; + xm?: number; + ym?: number; + a_locked?: boolean; + b_locked?: boolean; + section_title?: string; + line?: number[]; + shape?: string; + stroke_color?: string; + stroke_width?: number; + angle_deg?: number; + color?: string; + label_dx?: number; + label_dy?: number; + line_thickness?: number; + histogram?: unknown; +} + +// ── Preview Image Types ────────────────────────────────────────────── + +export interface PreviewPanel { + kind: 'image' | 'line_plot'; + title?: string; + image?: string; + line?: number[]; + fallback_image?: string; +} + +export interface PreviewPayload { + kind: 'image' | 'line_plot' | 'layer_gallery' | 'panels'; + image?: string; + line?: number[]; + layers?: Array<{ name?: string; image: string }>; + fallback_image?: string; + panels?: PreviewPanel[]; +} + +export type PreviewImage = string | PreviewPayload; + +// ── Node Data (attached to each ReactFlow node) ────────────────────── + +export interface GroupProxy { + handleId: string; + type: string; + label: string; + name: string; +} + +export interface NodeData extends Record { + label: string; + className: string; + definition?: NodeDefinition | null; + widgetValues: Record; + runtimeValues?: Record; + + // Execution results + previewImage?: PreviewImage | null; + tableRows?: Array> | null; + scalarValue?: number | { value: number | string; unit?: string } | null; + meshData?: unknown; + overlay?: OverlayData | null; + + // Status + error?: string | null; + warning?: string | null; + processingTimeMs?: number | null; + + // Group node fields + proxyInputs?: GroupProxy[]; + proxyOutputs?: GroupProxy[]; + childCount?: number; + collapsed?: boolean; + expandedSize?: { width: number; height: number }; + + // Serialization extras + extraData?: Record; + output?: string[]; + output_name?: string[]; +} + +// ── ReactFlow Node & Edge ──────────────────────────────────────────── + +export type TonoNode = Node; +export type TonoEdge = Edge<{ + groupProxyOwner?: string; + groupProxyOriginal?: { + source?: string; + sourceHandle?: string; + target?: string; + targetHandle?: string; + }; +}>; + +// ── Serialized Workflow ────────────────────────────────────────────── + +export interface SerializedNode { + id: string; + type?: string; + position: { x: number; y: number }; + width?: number; + height?: number; + className?: string; + parentId?: string; + extent?: [[number, number], [number, number]]; + hidden?: boolean; + style?: CSSProperties; + dragHandle?: string; + data: { + label: string; + className: string; + widgetValues: Record; + runtimeValues?: Record; + extraData?: Record; + output?: string[]; + output_name?: string[]; + }; +} + +export interface SerializedEdge { + id: string; + source: string; + sourceHandle?: string; + target: string; + targetHandle?: string; + style?: CSSProperties; + hidden?: boolean; + data?: Record; +} + +export interface SerializedWorkflow { + version: number; + nodes: SerializedNode[]; + edges: SerializedEdge[]; + packed?: boolean; + packedFiles?: Record; +} + +// ── WebSocket Messages ─────────────────────────────────────────────── + +export type WsMessage = + | { type: 'execution_start'; data: { prompt_id: string } } + | { type: 'executing'; data: { node: string; prompt_id: string } } + | { type: 'execution_complete'; data: { prompt_id: string } } + | { type: 'execution_error'; data: { node_id: string; message: string } } + | { type: 'preview'; data: { node_id: string; image: PreviewImage } } + | { type: 'table'; data: { node_id: string; rows: Array> } } + | { type: 'scalar'; data: { node_id: string; value: number | string; unit?: string } } + | { type: 'node_timing'; data: { node_id: string; elapsed_ms: number } } + | { type: 'mesh3d'; data: { node_id: string; mesh: unknown } } + | { type: 'overlay'; data: { node_id: string; overlay: OverlayData } } + | { type: 'node_warning'; data: { node_id: string; message: string } } + | { type: 'nodes_updated'; data: Record }; + +// ── Widget description (used by nodeWidgetLayout) ──────────────────── + +export interface WidgetDescriptor { + name: string; + type: string | string[]; + opts: InputOptions; + socketType?: string; +} + +// ── Node Context (provided by App to CustomNode) ───────────────────── + +export interface NodeContextValue { + executingNodeId: string | null; + onWidgetChange: (nodeId: string, name: string, value: unknown) => void; + openFileBrowser: (callback: (files: File[]) => void, options?: unknown) => void; + openHelp: (label: string) => void; + getTableColumns: (nodeId: string, inputName: string) => string[]; + getMeasurementChoices: (nodeId: string, inputName: string) => string[]; +} diff --git a/frontend/src/useUndoRedo.js b/frontend/src/useUndoRedo.ts similarity index 100% rename from frontend/src/useUndoRedo.js rename to frontend/src/useUndoRedo.ts diff --git a/frontend/src/valueFormatting.js b/frontend/src/valueFormatting.ts similarity index 100% rename from frontend/src/valueFormatting.js rename to frontend/src/valueFormatting.ts diff --git a/frontend/src/workflowCapture.js b/frontend/src/workflowCapture.ts similarity index 99% rename from frontend/src/workflowCapture.js rename to frontend/src/workflowCapture.ts index 7794e6d..22a1f57 100644 --- a/frontend/src/workflowCapture.js +++ b/frontend/src/workflowCapture.ts @@ -1,5 +1,5 @@ import { toBlob } from 'html-to-image'; -import { CANVAS_COLORS } from './constants.js'; +import { CANVAS_COLORS } from './constants.ts'; import { CAPTURE_SELECTOR as linePlotSelector } from './LinePlotOverlay'; import { CAPTURE_SELECTOR as thresholdSelector } from './ThresholdHistogram'; import { CAPTURE_SELECTOR as csSelector } from './CrossSectionOverlay'; diff --git a/frontend/src/workflowHydration.js b/frontend/src/workflowHydration.ts similarity index 96% rename from frontend/src/workflowHydration.js rename to frontend/src/workflowHydration.ts index 65afd66..8e183aa 100644 --- a/frontend/src/workflowHydration.js +++ b/frontend/src/workflowHydration.ts @@ -1,5 +1,5 @@ -import { sortNodesForParentOrder } from './nodeHierarchy.js'; -import { sanitizeRuntimeValuesForPersistence } from './runtimeValuePersistence.js'; +import { sortNodesForParentOrder } from './nodeHierarchy.ts'; +import { sanitizeRuntimeValuesForPersistence } from './runtimeValuePersistence.ts'; function mergeDefinition(nodeData, defs) { const savedData = nodeData || {}; diff --git a/frontend/src/workflowPacking.js b/frontend/src/workflowPacking.ts similarity index 99% rename from frontend/src/workflowPacking.js rename to frontend/src/workflowPacking.ts index fa571a7..c383c31 100644 --- a/frontend/src/workflowPacking.js +++ b/frontend/src/workflowPacking.ts @@ -5,7 +5,7 @@ * portable across machines and sessions. */ -import * as api from './api'; +import * as api from './api.ts'; const MAX_PACKED_BYTES = 100 * 1024 * 1024; // 100 MB diff --git a/frontend/src/workflowSerialization.js b/frontend/src/workflowSerialization.ts similarity index 99% rename from frontend/src/workflowSerialization.js rename to frontend/src/workflowSerialization.ts index 09beea5..ecb5a67 100644 --- a/frontend/src/workflowSerialization.js +++ b/frontend/src/workflowSerialization.ts @@ -1,4 +1,4 @@ -import { sanitizeRuntimeValuesForPersistence } from './runtimeValuePersistence.js'; +import { sanitizeRuntimeValuesForPersistence } from './runtimeValuePersistence.ts'; export function serializeWorkflowState(nodes, edges) { const compactObject = (value) => { diff --git a/frontend/tests/angleMeasureGeometry.test.mjs b/frontend/tests/angleMeasureGeometry.test.mjs index 159f506..2fcf4a4 100644 --- a/frontend/tests/angleMeasureGeometry.test.mjs +++ b/frontend/tests/angleMeasureGeometry.test.mjs @@ -6,7 +6,7 @@ import { getAngleLabelPosition, measureAngleDegrees, moveAngleWidget, -} from '../src/angleMeasureGeometry.js'; +} from '../src/angleMeasureGeometry.ts'; test('measureAngleDegrees returns the included angle', () => { assert.equal(measureAngleDegrees(0, 1, 0, 0, 1, 0), 90); diff --git a/frontend/tests/canvasInteractionTargets.test.mjs b/frontend/tests/canvasInteractionTargets.test.mjs index d51a2cf..fd48167 100644 --- a/frontend/tests/canvasInteractionTargets.test.mjs +++ b/frontend/tests/canvasInteractionTargets.test.mjs @@ -6,7 +6,7 @@ import { canStartCanvasRightDragZoomTarget, isEditableInteractionTarget, isSecondaryCanvasContextEvent, -} from '../src/canvasInteractionTargets.js'; +} from '../src/canvasInteractionTargets.ts'; function makeTarget(activeSelectors = []) { const selectorSet = new Set(activeSelectors); diff --git a/frontend/tests/connectionUtils.test.mjs b/frontend/tests/connectionUtils.test.mjs index 5582c28..0e49893 100644 --- a/frontend/tests/connectionUtils.test.mjs +++ b/frontend/tests/connectionUtils.test.mjs @@ -14,7 +14,7 @@ import { outputTypeCanConnectToTarget, resolveOutputTypeForTarget, checkConnectionValid, -} from '../src/connectionUtils.js'; +} from '../src/connectionUtils.ts'; // ── Handle ID helpers ───────────────────────────────────────────────── diff --git a/frontend/tests/constants.test.mjs b/frontend/tests/constants.test.mjs index 4834379..93bdc7d 100644 --- a/frontend/tests/constants.test.mjs +++ b/frontend/tests/constants.test.mjs @@ -6,7 +6,7 @@ import { getAcceptedSocketTypes, isDataSocketSpec, socketSpecAcceptsType, -} from '../src/constants.js'; +} from '../src/constants.ts'; test('intrinsic socket compatibility still allows INT to connect to FLOAT sockets', () => { assert.equal(socketSpecAcceptsType('INT', 'FLOAT'), true); diff --git a/frontend/tests/defaultWorkflow.test.mjs b/frontend/tests/defaultWorkflow.test.mjs index de6dffc..6651b59 100644 --- a/frontend/tests/defaultWorkflow.test.mjs +++ b/frontend/tests/defaultWorkflow.test.mjs @@ -1,8 +1,8 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { embedWorkflow } from '../src/pngMetadata.js'; -import { loadDefaultWorkflowAsset } from '../src/defaultWorkflow.js'; +import { embedWorkflow } from '../src/pngMetadata.ts'; +import { loadDefaultWorkflowAsset } from '../src/defaultWorkflow.ts'; const PNG_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aF9sAAAAASUVORK5CYII='; diff --git a/frontend/tests/executionGraph.test.mjs b/frontend/tests/executionGraph.test.mjs index a4aee59..4adaac2 100644 --- a/frontend/tests/executionGraph.test.mjs +++ b/frontend/tests/executionGraph.test.mjs @@ -5,7 +5,7 @@ import { serializeExecutionGraph, getAutoRunnableNodes, hasBlockingAutoRunInput, -} from '../src/executionGraph.js'; +} from '../src/executionGraph.ts'; test('serializeExecutionGraph excludes isolated nodes from the backend prompt', () => { const nodes = [ diff --git a/frontend/tests/groupDrag.test.mjs b/frontend/tests/groupDrag.test.mjs index 18ad72b..a92fc21 100644 --- a/frontend/tests/groupDrag.test.mjs +++ b/frontend/tests/groupDrag.test.mjs @@ -5,7 +5,7 @@ import { GROUP_DRAG_RELEASE_DISTANCE, getPointDistanceOutsideRect, shouldReleaseFromGroup, -} from '../src/groupDrag.js'; +} from '../src/groupDrag.ts'; test('getPointDistanceOutsideRect returns zero inside the rect', () => { const rect = { left: 10, top: 20, right: 110, bottom: 120 }; diff --git a/frontend/tests/groupSizing.test.mjs b/frontend/tests/groupSizing.test.mjs index 44082d0..3ef7df9 100644 --- a/frontend/tests/groupSizing.test.mjs +++ b/frontend/tests/groupSizing.test.mjs @@ -1,7 +1,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { getGroupMinimumSize } from '../src/groupSizing.js'; +import { getGroupMinimumSize } from '../src/groupSizing.ts'; test('getGroupMinimumSize keeps the base minimum for empty groups', () => { assert.deepEqual(getGroupMinimumSize([]), { width: 260, height: 180 }); diff --git a/frontend/tests/loadNodeOutputs.test.mjs b/frontend/tests/loadNodeOutputs.test.mjs index 0eb4b71..d3f5e29 100644 --- a/frontend/tests/loadNodeOutputs.test.mjs +++ b/frontend/tests/loadNodeOutputs.test.mjs @@ -5,7 +5,7 @@ import { beginTrackedNodeRequest, isTrackedNodeRequestCurrent, resolveLoadNodeChannelPath, -} from '../src/loadNodeOutputs.js'; +} from '../src/loadNodeOutputs.ts'; test('resolveLoadNodeChannelPath can resolve a new ImageDemo node from its explicit selection before mount', () => { const resolvedPath = resolveLoadNodeChannelPath({ diff --git a/frontend/tests/markupShapeGeometry.test.mjs b/frontend/tests/markupShapeGeometry.test.mjs index b0fb1bf..456166f 100644 --- a/frontend/tests/markupShapeGeometry.test.mjs +++ b/frontend/tests/markupShapeGeometry.test.mjs @@ -8,7 +8,7 @@ import { getMarkupPreviewStrokeWidth, sanitizeMarkupColor, sanitizeMarkupShape, -} from '../src/markupShapeGeometry.js'; +} from '../src/markupShapeGeometry.ts'; test('markup defaults use arrow and red', () => { assert.equal(MARKUP_DEFAULT_SHAPE, 'arrow'); diff --git a/frontend/tests/nodeClipboard.test.mjs b/frontend/tests/nodeClipboard.test.mjs index 6027f9b..3f83c82 100644 --- a/frontend/tests/nodeClipboard.test.mjs +++ b/frontend/tests/nodeClipboard.test.mjs @@ -7,7 +7,7 @@ import { instantiateNodeClipboardPayload, NODE_CLIPBOARD_KIND, parseNodeClipboardPayload, -} from '../src/nodeClipboard.js'; +} from '../src/nodeClipboard.ts'; test('buildNodeClipboardPayload keeps only selected nodes and internal edges', () => { const nodes = [ diff --git a/frontend/tests/nodeHierarchy.test.mjs b/frontend/tests/nodeHierarchy.test.mjs index 7dffff8..6998397 100644 --- a/frontend/tests/nodeHierarchy.test.mjs +++ b/frontend/tests/nodeHierarchy.test.mjs @@ -1,9 +1,9 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { sortNodesForParentOrder } from '../src/nodeHierarchy.js'; -import { hydrateWorkflowState } from '../src/workflowHydration.js'; -import { instantiateNodeClipboardPayload, NODE_CLIPBOARD_KIND } from '../src/nodeClipboard.js'; +import { sortNodesForParentOrder } from '../src/nodeHierarchy.ts'; +import { hydrateWorkflowState } from '../src/workflowHydration.ts'; +import { instantiateNodeClipboardPayload, NODE_CLIPBOARD_KIND } from '../src/nodeClipboard.ts'; test('sortNodesForParentOrder places parents before descendants', () => { const nodes = [ diff --git a/frontend/tests/nodeWidgetDefaults.test.mjs b/frontend/tests/nodeWidgetDefaults.test.mjs index c22df55..e547cf1 100644 --- a/frontend/tests/nodeWidgetDefaults.test.mjs +++ b/frontend/tests/nodeWidgetDefaults.test.mjs @@ -1,7 +1,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { buildDefaultWidgetValues, getDefaultWidgetValue } from '../src/nodeWidgetDefaults.js'; +import { buildDefaultWidgetValues, getDefaultWidgetValue } from '../src/nodeWidgetDefaults.ts'; test('enum widget defaults honor opts.default instead of the first option', () => { assert.equal( diff --git a/frontend/tests/nodeWidgetLayout.test.mjs b/frontend/tests/nodeWidgetLayout.test.mjs index 11d2f1d..1bad628 100644 --- a/frontend/tests/nodeWidgetLayout.test.mjs +++ b/frontend/tests/nodeWidgetLayout.test.mjs @@ -4,7 +4,7 @@ import assert from 'node:assert/strict'; import { buildCombinedInputNameByWidgetName, getWidgetCombinedInputName, -} from '../src/nodeWidgetLayout.js'; +} from '../src/nodeWidgetLayout.ts'; test('getWidgetCombinedInputName pairs same-label hide_when_input_connected widgets with their matching input', () => { const dataInputByName = new Map([ diff --git a/frontend/tests/pngMetadata.test.mjs b/frontend/tests/pngMetadata.test.mjs index cad6065..529d666 100644 --- a/frontend/tests/pngMetadata.test.mjs +++ b/frontend/tests/pngMetadata.test.mjs @@ -1,7 +1,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { embedWorkflow, extractWorkflow } from '../src/pngMetadata.js'; +import { embedWorkflow, extractWorkflow } from '../src/pngMetadata.ts'; const PNG_BASE64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aF9sAAAAASUVORK5CYII='; diff --git a/frontend/tests/valueFormatting.test.mjs b/frontend/tests/valueFormatting.test.mjs index d2f3847..c3d3101 100644 --- a/frontend/tests/valueFormatting.test.mjs +++ b/frontend/tests/valueFormatting.test.mjs @@ -6,7 +6,7 @@ import { formatDisplayUnit, formatTableRowCell, getTableColumns, -} from '../src/valueFormatting.js'; +} from '../src/valueFormatting.ts'; test('getTableColumns hides companion record-table unit columns', () => { const columns = getTableColumns([ diff --git a/frontend/tests/workflowCapture.test.mjs b/frontend/tests/workflowCapture.test.mjs index 09f160a..7d3f813 100644 --- a/frontend/tests/workflowCapture.test.mjs +++ b/frontend/tests/workflowCapture.test.mjs @@ -1,7 +1,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { OVERLAY_CAPTURE_SELECTORS, captureViewportBlob } from '../src/workflowCapture.js'; +import { OVERLAY_CAPTURE_SELECTORS, captureViewportBlob } from '../src/workflowCapture.ts'; function makeElement(name, { tagName = 'DIV', width = 160, height = 90, src = '' } = {}) { return { diff --git a/frontend/tests/workflowSerialization.test.mjs b/frontend/tests/workflowSerialization.test.mjs index 897c35f..4765ee6 100644 --- a/frontend/tests/workflowSerialization.test.mjs +++ b/frontend/tests/workflowSerialization.test.mjs @@ -1,8 +1,8 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { hydrateWorkflowState } from '../src/workflowHydration.js'; -import { serializeWorkflowState } from '../src/workflowSerialization.js'; +import { hydrateWorkflowState } from '../src/workflowHydration.ts'; +import { serializeWorkflowState } from '../src/workflowSerialization.ts'; test('serializeWorkflowState keeps only stable workflow fields needed for reload', () => { const nodes = [ diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..c6184ed --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "allowJs": true, + "strict": false, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true + }, + "include": ["src"] +}