Browse Source

Merge branch 'master' into develop

Resolve package.json conflict: take master's react-dom ^19.2.4,
keep develop's AI SDK and react-markdown additions.
Regenerate package-lock.json.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 6 months ago
parent
commit
4d27217bb1
  1. 207
      package-lock.json
  2. 4
      package.json
  3. 54
      src/app/api/browse-directory/__tests__/route.test.ts
  4. 35
      src/app/api/browse-directory/route.ts
  5. 6
      src/components/ProjectSetupModal.tsx
  6. 35
      src/components/__tests__/ProjectSetupModal.test.tsx

207
package-lock.json

@ -17,10 +17,10 @@
"autoprefixer": "^10.4.22",
"jszip": "^3.10.1",
"konva": "^10.0.12",
"next": "^16.0.6",
"next": "^16.0.7",
"postcss": "^8.5.6",
"react": "^19.2.0",
"react-dom": "^19.2.3",
"react-dom": "^19.2.4",
"react-konva": "^19.2.1",
"react-markdown": "^10.1.0",
"tailwindcss": "^4.1.17",
@ -56,13 +56,13 @@
"license": "MIT"
},
"node_modules/@ai-sdk/gateway": {
"version": "3.0.22",
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.22.tgz",
"integrity": "sha512-NgnlY73JNuooACHqUIz5uMOEWvqR1MMVbb2soGLMozLY1fgwEIF5iJFDAGa5/YArlzw2ATVU7zQu7HkR/FUjgA==",
"version": "3.0.29",
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.29.tgz",
"integrity": "sha512-zf6yXT+7DcVGWG7ntxVCYC48X/opsWlO5ePvgH8W9DaEVUtkemqKUEzBqowQ778PkZo8sqMnRfD0+fi9HamRRQ==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "3.0.5",
"@ai-sdk/provider-utils": "4.0.9",
"@ai-sdk/provider": "3.0.6",
"@ai-sdk/provider-utils": "4.0.11",
"@vercel/oidc": "3.1.0"
},
"engines": {
@ -73,13 +73,13 @@
}
},
"node_modules/@ai-sdk/google": {
"version": "3.0.13",
"resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-3.0.13.tgz",
"integrity": "sha512-HYCh8miS4FLxOIpjo/BmoFVMO5BuxNpHVVDQkoJotoH8ZSFftkJJGGayIxQT/Lwx9GGvVVCOQ+lCdBBAnkl1sA==",
"version": "3.0.18",
"resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-3.0.18.tgz",
"integrity": "sha512-g36Arlv38wjnqAhNlxMkfTvmWpMbD+FTFoXLppZ8dZhMQbr/UI5dEtFAByU1/lWeDyOmj/uTWzAuRTyJRw7OLA==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "3.0.5",
"@ai-sdk/provider-utils": "4.0.9"
"@ai-sdk/provider": "3.0.6",
"@ai-sdk/provider-utils": "4.0.11"
},
"engines": {
"node": ">=18"
@ -89,9 +89,9 @@
}
},
"node_modules/@ai-sdk/provider": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.5.tgz",
"integrity": "sha512-2Xmoq6DBJqmSl80U6V9z5jJSJP7ehaJJQMy2iFUqTay06wdCqTnPVBBQbtEL8RCChenL+q5DC5H5WzU3vV3v8w==",
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.6.tgz",
"integrity": "sha512-hSfoJtLtpMd7YxKM+iTqlJ0ZB+kJ83WESMiWuWrNVey3X8gg97x0OdAAaeAeclZByCX3UdPOTqhvJdK8qYA3ww==",
"license": "Apache-2.0",
"dependencies": {
"json-schema": "^0.4.0"
@ -101,12 +101,12 @@
}
},
"node_modules/@ai-sdk/provider-utils": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.9.tgz",
"integrity": "sha512-bB4r6nfhBOpmoS9mePxjRoCy+LnzP3AfhyMGCkGL4Mn9clVNlqEeKj26zEKEtB6yoSVcT1IQ0Zh9fytwMCDnow==",
"version": "4.0.11",
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.11.tgz",
"integrity": "sha512-y/WOPpcZaBjvNaogy83mBsCRPvbtaK0y1sY9ckRrrbTGMvG2HC/9Y/huqNXKnLAxUIME2PGa2uvF2CDwIsxoXQ==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider": "3.0.5",
"@ai-sdk/provider": "3.0.6",
"@standard-schema/spec": "^1.1.0",
"eventsource-parser": "^3.0.6"
},
@ -118,13 +118,13 @@
}
},
"node_modules/@ai-sdk/react": {
"version": "3.0.51",
"resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-3.0.51.tgz",
"integrity": "sha512-7nmCwEJM52NQZB4/ED8qJ4wbDg7EEWh94qJ7K9GSJxD6sWF3GOKrRZ5ivm4qNmKhY+JfCxCAxfghGY5mTKOsxw==",
"version": "3.0.66",
"resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-3.0.66.tgz",
"integrity": "sha512-bYxfXaNErVDiUaNlvNXaX+3oKLAeEyHiReJd54i+JTD3HEgRuazHfggzL0MidPnFJmlSZDHRRTGXxhMYh426QA==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/provider-utils": "4.0.9",
"ai": "6.0.49",
"@ai-sdk/provider-utils": "4.0.11",
"ai": "6.0.64",
"swr": "^2.2.5",
"throttleit": "2.1.0"
},
@ -1689,15 +1689,15 @@
}
},
"node_modules/@next/env": {
"version": "16.0.6",
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.6.tgz",
"integrity": "sha512-PFTK/G/vM3UJwK5XDYMFOqt8QW42mmhSgdKDapOlCqBUAOfJN2dyOnASR/xUR/JRrro0pLohh/zOJ77xUQWQAg==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz",
"integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==",
"license": "MIT"
},
"node_modules/@next/swc-darwin-arm64": {
"version": "16.0.6",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.6.tgz",
"integrity": "sha512-AGzKiPlDiui+9JcPRHLI4V9WFTTcKukhJTfK9qu3e0tz+Y/88B7vo5yZoO7UaikplJEHORzG3QaBFQfkjhnL0Q==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz",
"integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==",
"cpu": [
"arm64"
],
@ -1711,9 +1711,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "16.0.6",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.6.tgz",
"integrity": "sha512-LlLLNrK9WCIUkq2GciWDcquXYIf7vLxX8XE49gz7EncssZGL1vlHwgmURiJsUZAvk0HM1a8qb1ABDezsjAE/jw==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz",
"integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==",
"cpu": [
"x64"
],
@ -1727,9 +1727,9 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "16.0.6",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.6.tgz",
"integrity": "sha512-r04NzmLSGGfG8EPXKVK72N5zDNnq9pa9el78LhdtqIC3zqKh74QfKHnk24DoK4PEs6eY7sIK/CnNpt30oc59kg==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz",
"integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==",
"cpu": [
"arm64"
],
@ -1743,9 +1743,9 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "16.0.6",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.6.tgz",
"integrity": "sha512-hfB/QV0hA7lbD1OJxp52wVDlpffUMfyxUB5ysZbb/pBC5iuhyLcEKSVQo56PFUUmUQzbMsAtUu6k2Gh9bBtWXA==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz",
"integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==",
"cpu": [
"arm64"
],
@ -1759,9 +1759,9 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "16.0.6",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.6.tgz",
"integrity": "sha512-PZJushBgfvKhJBy01yXMdgL+l5XKr7uSn5jhOQXQXiH3iPT2M9iG64yHpPNGIKitKrHJInwmhPVGogZBAJOCPw==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz",
"integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==",
"cpu": [
"x64"
],
@ -1775,9 +1775,9 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "16.0.6",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.6.tgz",
"integrity": "sha512-LqY76IojrH9yS5fyATjLzlOIOgwyzBuNRqXwVxcGfZ58DWNQSyfnLGlfF6shAEqjwlDNLh4Z+P0rnOI87Y9jEw==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz",
"integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==",
"cpu": [
"x64"
],
@ -1791,9 +1791,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "16.0.6",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.6.tgz",
"integrity": "sha512-eIfSNNqAkj0tqKRf0u7BVjqylJCuabSrxnpSENY3YKApqwDMeAqYPmnOwmVe6DDl3Lvkbe7cJAyP6i9hQ5PmmQ==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz",
"integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==",
"cpu": [
"arm64"
],
@ -1807,9 +1807,9 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "16.0.6",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.6.tgz",
"integrity": "sha512-QGs18P4OKdK9y2F3Th42+KGnwsc2iaThOe6jxQgP62kslUU4W+g6AzI6bdIn/pslhSfxjAMU5SjakfT5Fyo/xA==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz",
"integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==",
"cpu": [
"x64"
],
@ -2424,6 +2424,60 @@
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
"version": "1.6.0",
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.1.0",
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
"version": "1.6.0",
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
"version": "1.1.0",
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
"version": "1.0.7",
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.5.0",
"@emnapi/runtime": "^1.5.0",
"@tybys/wasm-util": "^0.10.1"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
"version": "2.8.1",
"inBundle": true,
"license": "0BSD",
"optional": true
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.1.17",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz",
@ -3016,14 +3070,14 @@
}
},
"node_modules/ai": {
"version": "6.0.49",
"resolved": "https://registry.npmjs.org/ai/-/ai-6.0.49.tgz",
"integrity": "sha512-LABniBX/0R6Tv+iUK5keUZhZLaZUe4YjP5M2rZ4wAdZ8iKV3EfTAoJxuL1aaWTSJKIilKa9QUEkCgnp89/32bw==",
"version": "6.0.64",
"resolved": "https://registry.npmjs.org/ai/-/ai-6.0.64.tgz",
"integrity": "sha512-eH+6FC2THf0rXamPOBAPGNBuMvqv4gE5+IKoWVBrO1TkrjwRiuP3+P6yl7/HcXzN9JLbVe3lL8yaTIfn0ZwvxQ==",
"license": "Apache-2.0",
"dependencies": {
"@ai-sdk/gateway": "3.0.22",
"@ai-sdk/provider": "3.0.5",
"@ai-sdk/provider-utils": "4.0.9",
"@ai-sdk/gateway": "3.0.29",
"@ai-sdk/provider": "3.0.6",
"@ai-sdk/provider-utils": "4.0.11",
"@opentelemetry/api": "1.9.0"
},
"engines": {
@ -5456,13 +5510,14 @@
}
},
"node_modules/next": {
"version": "16.0.6",
"resolved": "https://registry.npmjs.org/next/-/next-16.0.6.tgz",
"integrity": "sha512-2zOZ/4FdaAp5hfCU/RnzARlZzBsjaTZ/XjNQmuyYLluAPM7kcrbIkdeO2SL0Ysd1vnrSgU+GwugfeWX1cUCgCg==",
"version": "16.1.6",
"resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz",
"integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==",
"license": "MIT",
"dependencies": {
"@next/env": "16.0.6",
"@next/env": "16.1.6",
"@swc/helpers": "0.5.15",
"baseline-browser-mapping": "^2.8.3",
"caniuse-lite": "^1.0.30001579",
"postcss": "8.4.31",
"styled-jsx": "5.1.6"
@ -5474,14 +5529,14 @@
"node": ">=20.9.0"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "16.0.6",
"@next/swc-darwin-x64": "16.0.6",
"@next/swc-linux-arm64-gnu": "16.0.6",
"@next/swc-linux-arm64-musl": "16.0.6",
"@next/swc-linux-x64-gnu": "16.0.6",
"@next/swc-linux-x64-musl": "16.0.6",
"@next/swc-win32-arm64-msvc": "16.0.6",
"@next/swc-win32-x64-msvc": "16.0.6",
"@next/swc-darwin-arm64": "16.1.6",
"@next/swc-darwin-x64": "16.1.6",
"@next/swc-linux-arm64-gnu": "16.1.6",
"@next/swc-linux-arm64-musl": "16.1.6",
"@next/swc-linux-x64-gnu": "16.1.6",
"@next/swc-linux-x64-musl": "16.1.6",
"@next/swc-win32-arm64-msvc": "16.1.6",
"@next/swc-win32-x64-msvc": "16.1.6",
"sharp": "^0.34.4"
},
"peerDependencies": {
@ -5802,24 +5857,24 @@
}
},
"node_modules/react": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"license": "MIT",
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.3"
"react": "^19.2.4"
}
},
"node_modules/react-is": {

4
package.json

@ -21,10 +21,10 @@
"autoprefixer": "^10.4.22",
"jszip": "^3.10.1",
"konva": "^10.0.12",
"next": "^16.0.6",
"next": "^16.0.7",
"postcss": "^8.5.6",
"react": "^19.2.0",
"react-dom": "^19.2.3",
"react-dom": "^19.2.4",
"react-konva": "^19.2.1",
"react-markdown": "^10.1.0",
"tailwindcss": "^4.1.17",

54
src/app/api/browse-directory/__tests__/route.test.ts

@ -0,0 +1,54 @@
import { describe, it, expect } from "vitest";
import { normalizeSelectedPath } from "../route";
describe("normalizeSelectedPath", () => {
it("should strip hostname prefix on macOS", () => {
expect(normalizeSelectedPath("AT-ALGKG9VR/Users/guy/Desktop", "darwin"))
.toBe("/Users/guy/Desktop");
});
it("should preserve absolute paths on macOS", () => {
expect(normalizeSelectedPath("/Users/guy/Desktop", "darwin"))
.toBe("/Users/guy/Desktop");
});
it("should remove trailing slash", () => {
expect(normalizeSelectedPath("/Users/guy/Desktop/", "darwin"))
.toBe("/Users/guy/Desktop");
});
it("should strip hostname and trailing slash", () => {
expect(normalizeSelectedPath("HOST/Users/guy/", "darwin"))
.toBe("/Users/guy");
});
it("should strip hostname prefix on Linux", () => {
expect(normalizeSelectedPath("hostname/home/user", "linux"))
.toBe("/home/user");
});
it("should not modify Windows drive paths", () => {
expect(normalizeSelectedPath("C:\\Users\\guy", "win32"))
.toBe("C:\\Users\\guy");
});
it("should preserve Windows drive root with backslash", () => {
expect(normalizeSelectedPath("C:\\", "win32"))
.toBe("C:\\");
});
it("should preserve Windows drive root with forward slash", () => {
expect(normalizeSelectedPath("C:/", "win32"))
.toBe("C:/");
});
it("should preserve Unix root /", () => {
expect(normalizeSelectedPath("/", "darwin"))
.toBe("/");
});
it("should leave hostname-only (no slash) as-is", () => {
expect(normalizeSelectedPath("HOSTNAME", "darwin"))
.toBe("HOSTNAME");
});
});

35
src/app/api/browse-directory/route.ts

@ -7,6 +7,33 @@ import { join } from "path";
const execAsync = promisify(exec);
/**
* Normalize a path returned by native directory pickers.
* On macOS, osascript can return hostname-prefixed paths for network volumes
* (e.g. "HOSTNAME/Users/..." instead of "/Users/..."). This strips the
* hostname prefix and cleans up trailing slashes.
*/
export function normalizeSelectedPath(selectedPath: string, platform: string): string {
// On macOS/Linux, ensure the path is absolute.
// osascript can return hostname-prefixed paths for network volumes
// e.g. "AT-ALGKG9VR/Users/guy/Desktop" instead of "/Users/guy/Desktop"
if ((platform === "darwin" || platform === "linux") && !selectedPath.startsWith("/")) {
const firstSlash = selectedPath.indexOf("/");
if (firstSlash >= 0) {
selectedPath = selectedPath.substring(firstSlash);
}
}
// Remove trailing slash/backslash (except root paths like "/" or "C:\")
if (selectedPath.length > 1 && (selectedPath.endsWith("/") || selectedPath.endsWith("\\"))) {
if (!(platform === "win32" && /^[A-Za-z]:[\\\/]$/.test(selectedPath))) {
selectedPath = selectedPath.slice(0, -1);
}
}
return selectedPath;
}
// GET: Open native directory picker and return the selected path
export async function GET() {
const platform = process.platform;
@ -121,13 +148,7 @@ if ($result) { Write-Output $result }
});
}
// Remove trailing slash/backslash if present (except for root paths like "/" or "C:\")
if (selectedPath.length > 1 && (selectedPath.endsWith("/") || selectedPath.endsWith("\\"))) {
// Don't remove trailing slash from Windows drive roots like "C:\"
if (!(platform === "win32" && /^[A-Za-z]:\\$/.test(selectedPath))) {
selectedPath = selectedPath.slice(0, -1);
}
}
selectedPath = normalizeSelectedPath(selectedPath, platform);
return NextResponse.json({
success: true,

6
src/components/ProjectSetupModal.tsx

@ -199,6 +199,12 @@ export function ProjectSetupModal({
return;
}
const trimmedPath = directoryPath.trim();
if (!(trimmedPath.startsWith("/") || /^[A-Za-z]:[\\\/]/.test(trimmedPath) || trimmedPath.startsWith("\\\\"))) {
setError("Project directory must be an absolute path (starting with /, a drive letter, or a UNC path)");
return;
}
setIsValidating(true);
setError(null);

35
src/components/__tests__/ProjectSetupModal.test.tsx

@ -382,6 +382,41 @@ describe("ProjectSetupModal", () => {
expect(onSave).not.toHaveBeenCalled();
});
it("should show error when path is not absolute", async () => {
const onSave = vi.fn();
render(
<ProjectSetupModal
isOpen={true}
onClose={vi.fn()}
onSave={onSave}
mode="new"
/>
);
// Fill name and a hostname-prefixed relative path
fireEvent.change(screen.getByPlaceholderText("my-project"), {
target: { value: "My Project" },
});
fireEvent.change(screen.getByPlaceholderText("/Users/username/projects/my-project"), {
target: { value: "AT-ALGKG9VR/Users/guy/Desktop/AI Project" },
});
// Click Create
fireEvent.click(screen.getByText("Create"));
await waitFor(() => {
expect(
screen.getByText("Project directory must be an absolute path (starting with /, a drive letter, or a UNC path)")
).toBeInTheDocument();
});
expect(onSave).not.toHaveBeenCalled();
// Validation should fail client-side without making a fetch to /api/workflow
expect(mockFetch).not.toHaveBeenCalledWith(
expect.stringContaining("/api/workflow")
);
});
it("should show error when path is not a directory", async () => {
mockFetch.mockImplementation((url: string) => {
if (url === "/api/env-status") {

Loading…
Cancel
Save