From bde82492aefef62bafa52726082aceb8416a66e9 Mon Sep 17 00:00:00 2001 From: DhanushSantosh Date: Tue, 13 Jan 2026 19:54:05 +0530 Subject: [PATCH 01/11] Revert "Sync branch state" This reverts commit 6704293cb11b81cf4bf5fb03e4e37a96fd56f2b7. --- package-lock.json | 91 +++++++++++++++++------------------------------ 1 file changed, 33 insertions(+), 58 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6fedaa7a..8cadeb00 100644 --- a/package-lock.json +++ b/package-lock.json @@ -679,6 +679,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1271,6 +1272,7 @@ "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.4.tgz", "integrity": "sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==", "license": "MIT", + "peer": true, "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -1313,6 +1315,7 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", + "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -2133,7 +2136,6 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, - "peer": true, "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", @@ -2155,7 +2157,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -2172,7 +2173,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -2187,7 +2187,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "engines": { "node": ">= 10.0.0" } @@ -2955,7 +2954,6 @@ "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", "license": "MIT", "optional": true, - "peer": true, "engines": { "node": ">=18" } @@ -3080,7 +3078,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -3097,7 +3094,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -3114,7 +3110,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -3223,7 +3218,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -3246,7 +3240,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -3269,7 +3262,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -3355,7 +3347,6 @@ ], "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, - "peer": true, "dependencies": { "@emnapi/runtime": "^1.7.0" }, @@ -3378,7 +3369,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -3398,7 +3388,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -3798,8 +3787,7 @@ "version": "16.0.10", "resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.10.tgz", "integrity": "sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { "version": "16.0.10", @@ -3813,7 +3801,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3830,7 +3817,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3847,7 +3833,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3864,7 +3849,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3881,7 +3865,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3898,7 +3881,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3915,7 +3897,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 10" } @@ -3932,7 +3913,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 10" } @@ -4032,6 +4012,7 @@ "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "playwright": "1.57.0" }, @@ -5472,7 +5453,6 @@ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.8.0" } @@ -5806,6 +5786,7 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.141.6.tgz", "integrity": "sha512-qWFxi2D6eGc1L03RzUuhyEOplZ7Q6q62YOl7Of9Y0q4YjwQwxRm4zxwDVtvUIoy4RLVCpqp5UoE+Nxv2PY9trg==", "license": "MIT", + "peer": true, "dependencies": { "@tanstack/history": "1.141.0", "@tanstack/react-store": "^0.8.0", @@ -6232,6 +6213,7 @@ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -6374,6 +6356,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6384,6 +6367,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -6489,6 +6473,7 @@ "integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.50.0", "@typescript-eslint/types": "8.50.0", @@ -6982,7 +6967,8 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@xyflow/react": { "version": "12.10.0", @@ -7080,6 +7066,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7140,6 +7127,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -7738,6 +7726,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -8269,8 +8258,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cliui": { "version": "8.0.1", @@ -8575,8 +8563,7 @@ "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true, "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/cross-env": { "version": "10.1.0", @@ -8673,6 +8660,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -8974,6 +8962,7 @@ "integrity": "sha512-59CAAjAhTaIMCN8y9kD573vDkxbs1uhDcrFLHSgutYdPcGOU35Rf95725snvzEOy4BFB7+eLJ8djCNPmGwG67w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "26.0.12", "builder-util": "26.0.11", @@ -9300,7 +9289,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", @@ -9321,7 +9309,6 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -9572,6 +9559,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9886,6 +9874,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -11553,7 +11542,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -11619,7 +11607,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -14061,7 +14048,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", @@ -14078,7 +14064,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "commander": "^9.4.0" }, @@ -14096,7 +14081,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "engines": { "node": "^12.20.0 || >=14" } @@ -14285,6 +14269,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -14294,6 +14279,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -14652,7 +14638,6 @@ "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -14841,6 +14826,7 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.4.0.tgz", "integrity": "sha512-BdrNXdzlofomLTiRnwJTSEAaGKyHHZkbMXIywOh7zlzp4uZnXErEwl9XZ+N1hJSNpeTtNxWvVwN0wUzAIQ4Hpg==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -14889,7 +14875,6 @@ "hasInstallScript": true, "license": "Apache-2.0", "optional": true, - "peer": true, "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", @@ -14940,7 +14925,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -14963,7 +14947,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -14986,7 +14969,6 @@ "os": [ "darwin" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -15003,7 +14985,6 @@ "os": [ "darwin" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -15020,7 +15001,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -15037,7 +15017,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -15054,7 +15033,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -15071,7 +15049,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -15088,7 +15065,6 @@ "os": [ "linux" ], - "peer": true, "funding": { "url": "https://opencollective.com/libvips" } @@ -15105,7 +15081,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -15128,7 +15103,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -15151,7 +15125,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -15174,7 +15147,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -15197,7 +15169,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -15220,7 +15191,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, @@ -15689,7 +15659,6 @@ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", "license": "MIT", - "peer": true, "dependencies": { "client-only": "0.0.1" }, @@ -15859,7 +15828,6 @@ "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" @@ -15923,7 +15891,6 @@ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -16021,6 +15988,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -16225,6 +16193,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16596,6 +16565,7 @@ "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -16685,7 +16655,8 @@ "resolved": "https://registry.npmjs.org/vite-plugin-electron-renderer/-/vite-plugin-electron-renderer-0.14.6.tgz", "integrity": "sha512-oqkWFa7kQIkvHXG7+Mnl1RTroA4sP0yesKatmAy0gjZC4VwUqlvF9IvOpHd1fpLWsqYX/eZlVxlhULNtaQ78Jw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/vite/node_modules/fdir": { "version": "6.5.0", @@ -16711,6 +16682,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -16753,6 +16725,7 @@ "integrity": "sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.0.16", "@vitest/mocker": "4.0.16", @@ -17010,6 +16983,7 @@ "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "dev": true, "license": "ISC", + "peer": true, "bin": { "yaml": "bin.mjs" }, @@ -17078,6 +17052,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.2.1.tgz", "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From c25efdc0d8a23bcf9163cc29e4a90af1b8bad7a1 Mon Sep 17 00:00:00 2001 From: DhanushSantosh Date: Tue, 13 Jan 2026 19:54:20 +0530 Subject: [PATCH 02/11] Revert "Wire provider model enablement into selectors" This reverts commit 8f1740c0f554c46c5e79229be6d9609d18bfeb10. --- .../board-view/shared/model-selector.tsx | 54 ++++--------------- .../model-defaults/phase-model-selector.tsx | 52 ++++++------------ 2 files changed, 28 insertions(+), 78 deletions(-) diff --git a/apps/ui/src/components/views/board-view/shared/model-selector.tsx b/apps/ui/src/components/views/board-view/shared/model-selector.tsx index dc95f39f..323190c8 100644 --- a/apps/ui/src/components/views/board-view/shared/model-selector.tsx +++ b/apps/ui/src/components/views/board-view/shared/model-selector.tsx @@ -4,6 +4,7 @@ import { Badge } from '@/components/ui/badge'; import { Brain, AlertTriangle } from 'lucide-react'; import { AnthropicIcon, CursorIcon, OpenAIIcon } from '@/components/ui/provider-icon'; import { cn } from '@/lib/utils'; +import type { ModelAlias } from '@/store/app-store'; import { useAppStore } from '@/store/app-store'; import { useSetupStore } from '@/store/setup-store'; import { getModelProvider, PROVIDER_PREFIXES, stripProviderPrefix } from '@automaker/types'; @@ -18,10 +19,6 @@ interface ModelSelectorProps { testIdPrefix?: string; } -const CODEX_EMPTY_AVAILABLE_MESSAGE = 'No Codex models available'; -const CODEX_EMPTY_ENABLED_MESSAGE = - 'No Codex models enabled. Enable models in Settings → AI Providers.'; - export function ModelSelector({ selectedModel, onModelSelect, @@ -30,8 +27,6 @@ export function ModelSelector({ const { enabledCursorModels, cursorDefaultModel, - enabledCodexModels, - codexDefaultModel, codexModels, codexModelsLoading, codexModelsError, @@ -54,10 +49,8 @@ export function ModelSelector({ } }, [isCodexAvailable, codexModels.length, codexModelsLoading, fetchCodexModels]); - const enabledCodexModelIds = new Set(enabledCodexModels); - // Transform codex models from store to ModelOption format - const codexModelOptions: ModelOption[] = codexModels.map((model) => { + const dynamicCodexModels: ModelOption[] = codexModels.map((model) => { // Infer badge based on tier let badge: string | undefined; if (model.tier === 'premium') badge = 'Premium'; @@ -74,10 +67,6 @@ export function ModelSelector({ }; }); - const enabledCodexModelOptions = codexModelOptions.filter((model) => - enabledCodexModelIds.has(model.id) - ); - // Filter Cursor models based on enabled models from global settings const filteredCursorModels = CURSOR_MODELS.filter((model) => { // Extract the cursor model ID from the prefixed ID (e.g., "cursor-auto" -> "auto") @@ -85,36 +74,21 @@ export function ModelSelector({ return enabledCursorModels.includes(cursorModelId as any); }); - const hasEnabledCodexModels = enabledCodexModelOptions.length > 0; - const codexDefaultSelection = - codexModelOptions.find((model) => model.id === codexDefaultModel)?.id || - enabledCodexModelOptions[0]?.id || - codexModelOptions[0]?.id; - const handleProviderChange = (provider: ModelProvider) => { if (provider === 'cursor' && selectedProvider !== 'cursor') { // Switch to Cursor's default model (from global settings) onModelSelect(`${PROVIDER_PREFIXES.cursor}${cursorDefaultModel}`); } else if (provider === 'codex' && selectedProvider !== 'codex') { - // Switch to Codex's default model (from global settings) - if (codexDefaultSelection) { - onModelSelect(codexDefaultSelection); - } + // Switch to Codex's default model (use isDefault flag from dynamic models) + const defaultModel = codexModels.find((m) => m.isDefault); + const defaultModelId = defaultModel?.id || codexModels[0]?.id || 'codex-gpt-5.2-codex'; + onModelSelect(defaultModelId); } else if (provider === 'claude' && selectedProvider !== 'claude') { // Switch to Claude's default model onModelSelect('sonnet'); } }; - const showCodexAvailableEmpty = - !codexModelsLoading && !codexModelsError && codexModelOptions.length === 0; - const showCodexEnabledEmpty = - !codexModelsLoading && - !codexModelsError && - codexModelOptions.length > 0 && - !hasEnabledCodexModels; - const showCodexList = !codexModelsLoading && !codexModelsError && hasEnabledCodexModels; - return (
{/* Provider Selection */} @@ -298,7 +272,7 @@ export function ModelSelector({
{/* Loading state */} - {codexModelsLoading && codexModelOptions.length === 0 && ( + {codexModelsLoading && dynamicCodexModels.length === 0 && (
Loading models... @@ -323,21 +297,15 @@ export function ModelSelector({ )} {/* Model list */} - {showCodexAvailableEmpty && ( + {!codexModelsLoading && !codexModelsError && dynamicCodexModels.length === 0 && (
- {CODEX_EMPTY_AVAILABLE_MESSAGE} + No Codex models available
)} - {showCodexEnabledEmpty && ( -
- {CODEX_EMPTY_ENABLED_MESSAGE} -
- )} - - {showCodexList && ( + {!codexModelsLoading && dynamicCodexModels.length > 0 && (
- {enabledCodexModelOptions.map((option) => { + {dynamicCodexModels.map((option) => { const isSelected = selectedModel === option.id; return ( + + +

Change default model and planning settings for new features

+
+ +
diff --git a/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx index 9912201d..ae7d655b 100644 --- a/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/edit-feature-dialog.tsx @@ -21,7 +21,8 @@ import { FeatureTextFilePath as DescriptionTextFilePath, ImagePreviewMap, } from '@/components/ui/description-image-dropzone'; -import { GitBranch, Cpu, FolderKanban } from 'lucide-react'; +import { GitBranch, Cpu, FolderKanban, Settings2 } from 'lucide-react'; +import { useNavigate } from '@tanstack/react-router'; import { toast } from 'sonner'; import { cn, modelSupportsThinking } from '@/lib/utils'; import { Feature, ModelAlias, ThinkingLevel, useAppStore, PlanningMode } from '@/store/app-store'; @@ -86,6 +87,7 @@ export function EditFeatureDialog({ isMaximized, allFeatures, }: EditFeatureDialogProps) { + const navigate = useNavigate(); const [editingFeature, setEditingFeature] = useState(feature); // Derive initial workMode from feature's branchName const [workMode, setWorkMode] = useState(() => { @@ -363,9 +365,31 @@ export function EditFeatureDialog({ {/* AI & Execution Section */}
-
- - AI & Execution +
+
+ + AI & Execution +
+ + + + + + +

Change default model and planning settings for new features

+
+
+
diff --git a/apps/ui/src/components/views/settings-view.tsx b/apps/ui/src/components/views/settings-view.tsx index aa6a8a84..2655e8a5 100644 --- a/apps/ui/src/components/views/settings-view.tsx +++ b/apps/ui/src/components/views/settings-view.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; +import { useSearch } from '@tanstack/react-router'; import { useAppStore } from '@/store/app-store'; -import { useSetupStore } from '@/store/setup-store'; import { useSettingsView, type SettingsViewId } from './settings-view/hooks'; import { NAV_ITEMS } from './settings-view/config/navigation'; @@ -51,6 +51,8 @@ export function SettingsView() { setDefaultPlanningMode, defaultRequirePlanApproval, setDefaultRequirePlanApproval, + defaultFeatureModel, + setDefaultFeatureModel, autoLoadClaudeMd, setAutoLoadClaudeMd, promptCustomization, @@ -86,8 +88,11 @@ export function SettingsView() { } }; + // Get initial view from URL search params + const { view: initialView } = useSearch({ from: '/settings' }); + // Use settings view navigation hook - const { activeView, navigateTo } = useSettingsView(); + const { activeView, navigateTo } = useSettingsView({ initialView }); // Handle navigation - if navigating to 'providers', default to 'claude-provider' const handleNavigate = (viewId: SettingsViewId) => { @@ -152,11 +157,13 @@ export function SettingsView() { skipVerificationInAutoMode={skipVerificationInAutoMode} defaultPlanningMode={defaultPlanningMode} defaultRequirePlanApproval={defaultRequirePlanApproval} + defaultFeatureModel={defaultFeatureModel} onDefaultSkipTestsChange={setDefaultSkipTests} onEnableDependencyBlockingChange={setEnableDependencyBlocking} onSkipVerificationInAutoModeChange={setSkipVerificationInAutoMode} onDefaultPlanningModeChange={setDefaultPlanningMode} onDefaultRequirePlanApprovalChange={setDefaultRequirePlanApproval} + onDefaultFeatureModelChange={setDefaultFeatureModel} /> ); case 'worktrees': diff --git a/apps/ui/src/components/views/settings-view/config/navigation.ts b/apps/ui/src/components/views/settings-view/config/navigation.ts index f63d0494..6a810973 100644 --- a/apps/ui/src/components/views/settings-view/config/navigation.ts +++ b/apps/ui/src/components/views/settings-view/config/navigation.ts @@ -37,8 +37,8 @@ export const GLOBAL_NAV_GROUPS: NavigationGroup[] = [ { label: 'Model & Prompts', items: [ - { id: 'model-defaults', label: 'Model Defaults', icon: Workflow }, { id: 'defaults', label: 'Feature Defaults', icon: FlaskConical }, + { id: 'model-defaults', label: 'Model Defaults', icon: Workflow }, { id: 'worktrees', label: 'Worktrees', icon: GitBranch }, { id: 'prompts', label: 'Prompt Customization', icon: MessageSquareText }, { id: 'api-keys', label: 'API Keys', icon: Key }, diff --git a/apps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx b/apps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx index c3b4e9ae..956d28fa 100644 --- a/apps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx +++ b/apps/ui/src/components/views/settings-view/feature-defaults/feature-defaults-section.tsx @@ -10,6 +10,7 @@ import { ScrollText, ShieldCheck, FastForward, + Cpu, } from 'lucide-react'; import { cn } from '@/lib/utils'; import { @@ -19,6 +20,8 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; +import type { PhaseModelEntry } from '@automaker/types'; +import { PhaseModelSelector } from '../model-defaults/phase-model-selector'; type PlanningMode = 'skip' | 'lite' | 'spec' | 'full'; @@ -28,11 +31,13 @@ interface FeatureDefaultsSectionProps { skipVerificationInAutoMode: boolean; defaultPlanningMode: PlanningMode; defaultRequirePlanApproval: boolean; + defaultFeatureModel: PhaseModelEntry; onDefaultSkipTestsChange: (value: boolean) => void; onEnableDependencyBlockingChange: (value: boolean) => void; onSkipVerificationInAutoModeChange: (value: boolean) => void; onDefaultPlanningModeChange: (value: PlanningMode) => void; onDefaultRequirePlanApprovalChange: (value: boolean) => void; + onDefaultFeatureModelChange: (value: PhaseModelEntry) => void; } export function FeatureDefaultsSection({ @@ -41,11 +46,13 @@ export function FeatureDefaultsSection({ skipVerificationInAutoMode, defaultPlanningMode, defaultRequirePlanApproval, + defaultFeatureModel, onDefaultSkipTestsChange, onEnableDependencyBlockingChange, onSkipVerificationInAutoModeChange, onDefaultPlanningModeChange, onDefaultRequirePlanApprovalChange, + onDefaultFeatureModelChange, }: FeatureDefaultsSectionProps) { return (
+ {/* Default Feature Model Setting */} +
+
+ +
+
+
+ + +
+

+ The default AI model and thinking level used when creating new feature cards. +

+
+
+ + {/* Separator */} +
+ {/* Planning Mode Default */}
-
)} {/* Separator */} - {defaultPlanningMode === 'skip' &&
} +
{/* Automated Testing Setting */}
diff --git a/apps/ui/src/hooks/use-settings-migration.ts b/apps/ui/src/hooks/use-settings-migration.ts index bb86c10c..e4375cbf 100644 --- a/apps/ui/src/hooks/use-settings-migration.ts +++ b/apps/ui/src/hooks/use-settings-migration.ts @@ -562,6 +562,7 @@ export function hydrateStoreFromSettings(settings: GlobalSettings): void { useWorktrees: settings.useWorktrees ?? true, defaultPlanningMode: settings.defaultPlanningMode ?? 'skip', defaultRequirePlanApproval: settings.defaultRequirePlanApproval ?? false, + defaultFeatureModel: settings.defaultFeatureModel ?? { model: 'opus' }, muteDoneSound: settings.muteDoneSound ?? false, enhancementModel: settings.enhancementModel ?? 'sonnet', validationModel: settings.validationModel ?? 'opus', diff --git a/apps/ui/src/hooks/use-settings-sync.ts b/apps/ui/src/hooks/use-settings-sync.ts index 41ef6693..1fb9dbd0 100644 --- a/apps/ui/src/hooks/use-settings-sync.ts +++ b/apps/ui/src/hooks/use-settings-sync.ts @@ -42,6 +42,7 @@ const SETTINGS_FIELDS_TO_SYNC = [ 'useWorktrees', 'defaultPlanningMode', 'defaultRequirePlanApproval', + 'defaultFeatureModel', 'muteDoneSound', 'enhancementModel', 'validationModel', @@ -466,6 +467,7 @@ export async function refreshSettingsFromServer(): Promise { useWorktrees: serverSettings.useWorktrees, defaultPlanningMode: serverSettings.defaultPlanningMode, defaultRequirePlanApproval: serverSettings.defaultRequirePlanApproval, + defaultFeatureModel: serverSettings.defaultFeatureModel ?? { model: 'opus' }, muteDoneSound: serverSettings.muteDoneSound, enhancementModel: serverSettings.enhancementModel, validationModel: serverSettings.validationModel, diff --git a/apps/ui/src/routes/settings.tsx b/apps/ui/src/routes/settings.tsx index 74170d94..c509e93a 100644 --- a/apps/ui/src/routes/settings.tsx +++ b/apps/ui/src/routes/settings.tsx @@ -1,6 +1,16 @@ import { createFileRoute } from '@tanstack/react-router'; import { SettingsView } from '@/components/views/settings-view'; +import type { SettingsViewId } from '@/components/views/settings-view/hooks'; + +interface SettingsSearchParams { + view?: SettingsViewId; +} export const Route = createFileRoute('/settings')({ component: SettingsView, + validateSearch: (search: Record): SettingsSearchParams => { + return { + view: search.view as SettingsViewId | undefined, + }; + }, }); diff --git a/apps/ui/src/store/app-store.ts b/apps/ui/src/store/app-store.ts index 36aec5ed..280ba7c1 100644 --- a/apps/ui/src/store/app-store.ts +++ b/apps/ui/src/store/app-store.ts @@ -657,6 +657,7 @@ export interface AppState { defaultPlanningMode: PlanningMode; defaultRequirePlanApproval: boolean; + defaultFeatureModel: PhaseModelEntry; // Plan Approval State // When a plan requires user approval, this holds the pending approval details @@ -1104,6 +1105,7 @@ export interface AppActions { setDefaultPlanningMode: (mode: PlanningMode) => void; setDefaultRequirePlanApproval: (require: boolean) => void; + setDefaultFeatureModel: (entry: PhaseModelEntry) => void; // Plan Approval actions setPendingPlanApproval: ( @@ -1277,6 +1279,7 @@ const initialState: AppState = { specCreatingForProject: null, defaultPlanningMode: 'skip' as PlanningMode, defaultRequirePlanApproval: false, + defaultFeatureModel: { model: 'opus' } as PhaseModelEntry, pendingPlanApproval: null, claudeRefreshInterval: 60, claudeUsage: null, @@ -3093,6 +3096,7 @@ export const useAppStore = create()((set, get) => ({ setDefaultPlanningMode: (mode) => set({ defaultPlanningMode: mode }), setDefaultRequirePlanApproval: (require) => set({ defaultRequirePlanApproval: require }), + setDefaultFeatureModel: (entry) => set({ defaultFeatureModel: entry }), // Plan Approval actions setPendingPlanApproval: (approval) => set({ pendingPlanApproval: approval }), diff --git a/libs/types/src/settings.ts b/libs/types/src/settings.ts index 38402c24..a4efa469 100644 --- a/libs/types/src/settings.ts +++ b/libs/types/src/settings.ts @@ -375,6 +375,8 @@ export interface GlobalSettings { defaultPlanningMode: PlanningMode; /** Default: require manual approval before generating */ defaultRequirePlanApproval: boolean; + /** Default model and thinking level for new feature cards */ + defaultFeatureModel: PhaseModelEntry; // Audio Preferences /** Mute completion notification sound */ @@ -698,6 +700,7 @@ export const DEFAULT_GLOBAL_SETTINGS: GlobalSettings = { useWorktrees: true, defaultPlanningMode: 'skip', defaultRequirePlanApproval: false, + defaultFeatureModel: { model: 'opus' }, muteDoneSound: false, phaseModels: DEFAULT_PHASE_MODELS, enhancementModel: 'sonnet', From ff5915dd2071a21514f3b3ff7e9c5259770b2eb3 Mon Sep 17 00:00:00 2001 From: webdevcody Date: Tue, 13 Jan 2026 12:19:24 -0500 Subject: [PATCH 04/11] refactor: update terminology in board view components - Renamed "Worktrees" to "Worktree Bar" in the BoardHeader component for clarity. - Updated comments and labels in AddFeatureDialog, PlanSettingsDialog, and WorktreeSettingsDialog to reflect the new terminology and improve user understanding of worktree mode functionality. --- apps/ui/src/components/views/board-view/board-header.tsx | 2 +- .../views/board-view/dialogs/add-feature-dialog.tsx | 2 +- .../views/board-view/dialogs/plan-settings-dialog.tsx | 6 +++--- .../views/board-view/dialogs/worktree-settings-dialog.tsx | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/ui/src/components/views/board-view/board-header.tsx b/apps/ui/src/components/views/board-view/board-header.tsx index cfaa8a27..2f6e4b5e 100644 --- a/apps/ui/src/components/views/board-view/board-header.tsx +++ b/apps/ui/src/components/views/board-view/board-header.tsx @@ -138,7 +138,7 @@ export function BoardHeader({
- Use selected worktree branch + Default to worktree mode

- When enabled, features created via the Plan dialog will be assigned to the currently - selected worktree branch. When disabled, features will be added to the main branch. + Planned features will automatically use isolated worktrees, keeping changes separate + from your main branch until you're ready to merge.

diff --git a/apps/ui/src/components/views/board-view/dialogs/worktree-settings-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/worktree-settings-dialog.tsx index da7cb134..c742e631 100644 --- a/apps/ui/src/components/views/board-view/dialogs/worktree-settings-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/worktree-settings-dialog.tsx @@ -45,7 +45,7 @@ export function WorktreeSettingsDialog({ className="text-sm font-medium cursor-pointer flex items-center gap-2" > - Use selected worktree branch + Default to worktree mode

- When enabled, the Add Feature dialog will default to custom branch mode with the - currently selected worktree branch pre-filled. + New features will automatically use isolated worktrees, keeping changes separate + from your main branch until you're ready to merge.

From 3bd8626d488a214992346dfdc9db4ad5c7eb9b09 Mon Sep 17 00:00:00 2001 From: Shirone Date: Tue, 13 Jan 2026 18:27:22 +0100 Subject: [PATCH 05/11] feat: add branch/worktree support to mass edit dialog Implement worktree creation and branch assignment in the mass edit dialog to match the functionality of the add-feature and edit-feature dialogs. Changes: - Add WorkModeSelector to mass-edit-dialog.tsx with three modes: - 'Current Branch': Work on current branch (no worktree) - 'Auto Worktree': Auto-generate branch name and create worktree - 'Custom Branch': Use specified branch name and create worktree - Update handleBulkUpdate in board-view.tsx to: - Accept workMode parameter - Create worktrees for 'auto' and 'custom' modes - Auto-select created worktrees in the board header - Handle branch name generation for 'auto' mode - Add necessary props to MassEditDialog (branchSuggestions, branchCardCounts, currentBranch) Users can now bulk-assign features to a branch and automatically create/select worktrees, enabling efficient project setup with many features. Fixes #459 Co-Authored-By: Claude Sonnet 4.5 --- apps/ui/src/components/views/board-view.tsx | 107 +++++++++++++++++- .../board-view/dialogs/mass-edit-dialog.tsx | 65 ++++++++++- 2 files changed, 164 insertions(+), 8 deletions(-) diff --git a/apps/ui/src/components/views/board-view.tsx b/apps/ui/src/components/views/board-view.tsx index 30cd4db3..58136ebc 100644 --- a/apps/ui/src/components/views/board-view.tsx +++ b/apps/ui/src/components/views/board-view.tsx @@ -492,18 +492,104 @@ export function BoardView() { // Handler for bulk updating multiple features const handleBulkUpdate = useCallback( - async (updates: Partial) => { + async (updates: Partial, workMode: 'current' | 'auto' | 'custom') => { if (!currentProject || selectedFeatureIds.size === 0) return; try { + // Determine final branch name based on work mode: + // - 'current': No branch name, work on current branch (no worktree) + // - 'auto': Auto-generate branch name based on current branch + // - 'custom': Use the provided branch name + let finalBranchName: string | undefined; + + if (workMode === 'current') { + // No worktree isolation - work directly on current branch + finalBranchName = undefined; + } else if (workMode === 'auto') { + // Auto-generate a branch name based on current branch and timestamp + const baseBranch = currentWorktreeBranch || 'main'; + const timestamp = Date.now(); + const randomSuffix = Math.random().toString(36).substring(2, 6); + finalBranchName = `feature/${baseBranch}-${timestamp}-${randomSuffix}`; + } else { + // Custom mode - use provided branch name + finalBranchName = updates.branchName || undefined; + } + + // Create worktree for 'auto' or 'custom' modes when we have a branch name + if ((workMode === 'auto' || workMode === 'custom') && finalBranchName) { + try { + const electronApi = getElectronAPI(); + if (electronApi?.worktree?.create) { + const result = await electronApi.worktree.create( + currentProject.path, + finalBranchName + ); + if (result.success && result.worktree) { + logger.info( + `Worktree for branch "${finalBranchName}" ${ + result.worktree?.isNew ? 'created' : 'already exists' + }` + ); + // Auto-select the worktree when creating/using it for bulk update + const currentWorktrees = getWorktrees(currentProject.path); + const existingWorktree = currentWorktrees.find( + (w) => w.branch === result.worktree.branch + ); + + // Only add if it doesn't already exist (to avoid duplicates) + if (!existingWorktree) { + const newWorktreeInfo = { + path: result.worktree.path, + branch: result.worktree.branch, + isMain: false, + isCurrent: false, + hasWorktree: true, + }; + setWorktrees(currentProject.path, [...currentWorktrees, newWorktreeInfo]); + } + // Select the worktree (whether it existed or was just added) + setCurrentWorktree( + currentProject.path, + result.worktree.path, + result.worktree.branch + ); + // Refresh worktree list in UI + setWorktreeRefreshKey((k) => k + 1); + } else if (!result.success) { + logger.error( + `Failed to create worktree for branch "${finalBranchName}":`, + result.error + ); + toast.error('Failed to create worktree', { + description: result.error || 'An error occurred', + }); + return; // Don't proceed with update if worktree creation failed + } + } + } catch (error) { + logger.error('Error creating worktree:', error); + toast.error('Failed to create worktree', { + description: error instanceof Error ? error.message : 'An error occurred', + }); + return; // Don't proceed with update if worktree creation failed + } + } + + // Use the final branch name in updates + const finalUpdates = { + ...updates, + branchName: finalBranchName, + }; + const api = getHttpApiClient(); const featureIds = Array.from(selectedFeatureIds); - const result = await api.features.bulkUpdate(currentProject.path, featureIds, updates); + const result = await api.features.bulkUpdate(currentProject.path, featureIds, finalUpdates); if (result.success) { // Update local state featureIds.forEach((featureId) => { - updateFeature(featureId, updates); + updateFeature(featureId, finalUpdates); }); toast.success(`Updated ${result.updatedCount} features`); exitSelectionMode(); @@ -517,7 +603,17 @@ export function BoardView() { toast.error('Failed to update features'); } }, - [currentProject, selectedFeatureIds, updateFeature, exitSelectionMode] + [ + currentProject, + selectedFeatureIds, + updateFeature, + exitSelectionMode, + currentWorktreeBranch, + getWorktrees, + setWorktrees, + setCurrentWorktree, + setWorktreeRefreshKey, + ] ); // Handler for bulk deleting multiple features @@ -1325,6 +1421,9 @@ export function BoardView() { onClose={() => setShowMassEditDialog(false)} selectedFeatures={selectedFeatures} onApply={handleBulkUpdate} + branchSuggestions={branchSuggestions} + branchCardCounts={branchCardCounts} + currentBranch={currentWorktreeBranch || undefined} /> {/* Board Background Modal */} diff --git a/apps/ui/src/components/views/board-view/dialogs/mass-edit-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/mass-edit-dialog.tsx index 30042a4c..2be7d32f 100644 --- a/apps/ui/src/components/views/board-view/dialogs/mass-edit-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/mass-edit-dialog.tsx @@ -13,7 +13,8 @@ import { Label } from '@/components/ui/label'; import { AlertCircle } from 'lucide-react'; import { modelSupportsThinking } from '@/lib/utils'; import { Feature, ModelAlias, ThinkingLevel, PlanningMode } from '@/store/app-store'; -import { TestingTabContent, PrioritySelect, PlanningModeSelect } from '../shared'; +import { TestingTabContent, PrioritySelect, PlanningModeSelect, WorkModeSelector } from '../shared'; +import type { WorkMode } from '../shared'; import { PhaseModelSelector } from '@/components/views/settings-view/model-defaults/phase-model-selector'; import { isCursorModel, isClaudeModel, type PhaseModelEntry } from '@automaker/types'; import { cn } from '@/lib/utils'; @@ -23,7 +24,10 @@ interface MassEditDialogProps { open: boolean; onClose: () => void; selectedFeatures: Feature[]; - onApply: (updates: Partial) => Promise; + onApply: (updates: Partial, workMode: WorkMode) => Promise; + branchSuggestions: string[]; + branchCardCounts?: Record; + currentBranch?: string; } interface ApplyState { @@ -33,6 +37,7 @@ interface ApplyState { requirePlanApproval: boolean; priority: boolean; skipTests: boolean; + branchName: boolean; } function getMixedValues(features: Feature[]): Record { @@ -47,6 +52,7 @@ function getMixedValues(features: Feature[]): Record { ), priority: !features.every((f) => f.priority === first.priority), skipTests: !features.every((f) => f.skipTests === first.skipTests), + branchName: !features.every((f) => f.branchName === first.branchName), }; } @@ -97,7 +103,15 @@ function FieldWrapper({ label, isMixed, willApply, onApplyChange, children }: Fi ); } -export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: MassEditDialogProps) { +export function MassEditDialog({ + open, + onClose, + selectedFeatures, + onApply, + branchSuggestions, + branchCardCounts, + currentBranch, +}: MassEditDialogProps) { const [isApplying, setIsApplying] = useState(false); // Track which fields to apply @@ -108,6 +122,7 @@ export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: Mas requirePlanApproval: false, priority: false, skipTests: false, + branchName: false, }); // Field values @@ -118,6 +133,18 @@ export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: Mas const [priority, setPriority] = useState(2); const [skipTests, setSkipTests] = useState(false); + // Work mode and branch name state + const [workMode, setWorkMode] = useState(() => { + // Derive initial work mode from first selected feature's branchName + if (selectedFeatures.length > 0 && selectedFeatures[0].branchName) { + return 'custom'; + } + return 'current'; + }); + const [branchName, setBranchName] = useState(() => { + return getInitialValue(selectedFeatures, 'branchName', '') as string; + }); + // Calculate mixed values const mixedValues = useMemo(() => getMixedValues(selectedFeatures), [selectedFeatures]); @@ -131,6 +158,7 @@ export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: Mas requirePlanApproval: false, priority: false, skipTests: false, + branchName: false, }); setModel(getInitialValue(selectedFeatures, 'model', 'sonnet') as ModelAlias); setThinkingLevel(getInitialValue(selectedFeatures, 'thinkingLevel', 'none') as ThinkingLevel); @@ -138,6 +166,10 @@ export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: Mas setRequirePlanApproval(getInitialValue(selectedFeatures, 'requirePlanApproval', false)); setPriority(getInitialValue(selectedFeatures, 'priority', 2)); setSkipTests(getInitialValue(selectedFeatures, 'skipTests', false)); + // Reset work mode and branch name + const initialBranchName = getInitialValue(selectedFeatures, 'branchName', '') as string; + setBranchName(initialBranchName); + setWorkMode(initialBranchName ? 'custom' : 'current'); } }, [open, selectedFeatures]); @@ -150,6 +182,12 @@ export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: Mas if (applyState.requirePlanApproval) updates.requirePlanApproval = requirePlanApproval; if (applyState.priority) updates.priority = priority; if (applyState.skipTests) updates.skipTests = skipTests; + if (applyState.branchName) { + // For 'current' mode, use empty string (work on current branch) + // For 'auto' mode, use empty string (will be auto-generated) + // For 'custom' mode, use the specified branch name + updates.branchName = workMode === 'custom' ? branchName : ''; + } if (Object.keys(updates).length === 0) { onClose(); @@ -158,7 +196,7 @@ export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: Mas setIsApplying(true); try { - await onApply(updates); + await onApply(updates, workMode); onClose(); } finally { setIsApplying(false); @@ -293,6 +331,25 @@ export function MassEditDialog({ open, onClose, selectedFeatures, onApply }: Mas testIdPrefix="mass-edit" /> + + {/* Branch / Work Mode */} + setApplyState((prev) => ({ ...prev, branchName: apply }))} + > + +
From d4076ad0cef4932d42c2cb02cdc3d20f3dd31c46 Mon Sep 17 00:00:00 2001 From: Shirone Date: Tue, 13 Jan 2026 18:37:26 +0100 Subject: [PATCH 06/11] refactor: address CodeRabbit PR feedback Improvements based on CodeRabbit review comments: 1. Use getPrimaryWorktreeBranch for consistent branch detection - Replace hardcoded 'main' fallback with getPrimaryWorktreeBranch() - Ensures auto-generated branch names respect the repo's actual primary branch - Handles repos using 'master' or other primary branch names 2. Extract worktree auto-selection logic to helper function - Create addAndSelectWorktree helper to eliminate code duplication - Use helper in both onWorktreeAutoSelect and handleBulkUpdate - Reduces maintenance burden and ensures consistent behavior These changes improve code consistency and maintainability without affecting functionality. Co-Authored-By: Claude Sonnet 4.5 --- apps/ui/src/components/views/board-view.tsx | 77 +++++++++------------ 1 file changed, 31 insertions(+), 46 deletions(-) diff --git a/apps/ui/src/components/views/board-view.tsx b/apps/ui/src/components/views/board-view.tsx index 58136ebc..8f43b677 100644 --- a/apps/ui/src/components/views/board-view.tsx +++ b/apps/ui/src/components/views/board-view.tsx @@ -422,6 +422,31 @@ export function BoardView() { const selectedWorktreeBranch = currentWorktreeBranch || worktrees.find((w) => w.isMain)?.branch || 'main'; + // Helper function to add and select a worktree + const addAndSelectWorktree = useCallback( + (worktreeResult: { path: string; branch: string }) => { + if (!currentProject) return; + + const currentWorktrees = getWorktrees(currentProject.path); + const existingWorktree = currentWorktrees.find((w) => w.branch === worktreeResult.branch); + + // Only add if it doesn't already exist (to avoid duplicates) + if (!existingWorktree) { + const newWorktreeInfo = { + path: worktreeResult.path, + branch: worktreeResult.branch, + isMain: false, + isCurrent: false, + hasWorktree: true, + }; + setWorktrees(currentProject.path, [...currentWorktrees, newWorktreeInfo]); + } + // Select the worktree (whether it existed or was just added) + setCurrentWorktree(currentProject.path, worktreeResult.path, worktreeResult.branch); + }, + [currentProject, getWorktrees, setWorktrees, setCurrentWorktree] + ); + // Extract all action handlers into a hook const { handleAddFeature, @@ -467,26 +492,7 @@ export function BoardView() { outputFeature, projectPath: currentProject?.path || null, onWorktreeCreated: () => setWorktreeRefreshKey((k) => k + 1), - onWorktreeAutoSelect: (newWorktree) => { - if (!currentProject) return; - // Check if worktree already exists in the store (by branch name) - const currentWorktrees = getWorktrees(currentProject.path); - const existingWorktree = currentWorktrees.find((w) => w.branch === newWorktree.branch); - - // Only add if it doesn't already exist (to avoid duplicates) - if (!existingWorktree) { - const newWorktreeInfo = { - path: newWorktree.path, - branch: newWorktree.branch, - isMain: false, - isCurrent: false, - hasWorktree: true, - }; - setWorktrees(currentProject.path, [...currentWorktrees, newWorktreeInfo]); - } - // Select the worktree (whether it existed or was just added) - setCurrentWorktree(currentProject.path, newWorktree.path, newWorktree.branch); - }, + onWorktreeAutoSelect: addAndSelectWorktree, currentWorktreeBranch, }); @@ -507,7 +513,8 @@ export function BoardView() { finalBranchName = undefined; } else if (workMode === 'auto') { // Auto-generate a branch name based on current branch and timestamp - const baseBranch = currentWorktreeBranch || 'main'; + const baseBranch = + currentWorktreeBranch || getPrimaryWorktreeBranch(currentProject.path) || 'main'; const timestamp = Date.now(); const randomSuffix = Math.random().toString(36).substring(2, 6); finalBranchName = `feature/${baseBranch}-${timestamp}-${randomSuffix}`; @@ -532,28 +539,7 @@ export function BoardView() { }` ); // Auto-select the worktree when creating/using it for bulk update - const currentWorktrees = getWorktrees(currentProject.path); - const existingWorktree = currentWorktrees.find( - (w) => w.branch === result.worktree.branch - ); - - // Only add if it doesn't already exist (to avoid duplicates) - if (!existingWorktree) { - const newWorktreeInfo = { - path: result.worktree.path, - branch: result.worktree.branch, - isMain: false, - isCurrent: false, - hasWorktree: true, - }; - setWorktrees(currentProject.path, [...currentWorktrees, newWorktreeInfo]); - } - // Select the worktree (whether it existed or was just added) - setCurrentWorktree( - currentProject.path, - result.worktree.path, - result.worktree.branch - ); + addAndSelectWorktree(result.worktree); // Refresh worktree list in UI setWorktreeRefreshKey((k) => k + 1); } else if (!result.success) { @@ -609,9 +595,8 @@ export function BoardView() { updateFeature, exitSelectionMode, currentWorktreeBranch, - getWorktrees, - setWorktrees, - setCurrentWorktree, + getPrimaryWorktreeBranch, + addAndSelectWorktree, setWorktreeRefreshKey, ] ); From cc4f39a6abb80dc660281dda3d53dd6bf94fc7f0 Mon Sep 17 00:00:00 2001 From: Shirone Date: Tue, 13 Jan 2026 18:38:09 +0100 Subject: [PATCH 07/11] chore: fix formatting issues for CI Fix Prettier formatting in two files: - apps/server/src/lib/sdk-options.ts: Split long arrays to one item per line - docs/docker-isolation.md: Align markdown table columns Resolves CI format check failures. Co-Authored-By: Claude Sonnet 4.5 --- apps/server/src/lib/sdk-options.ts | 24 ++++++++++++++++++++++-- docs/docker-isolation.md | 15 +++++++-------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/apps/server/src/lib/sdk-options.ts b/apps/server/src/lib/sdk-options.ts index ff3d6067..cc1df2f5 100644 --- a/apps/server/src/lib/sdk-options.ts +++ b/apps/server/src/lib/sdk-options.ts @@ -129,10 +129,30 @@ export const TOOL_PRESETS = { specGeneration: ['Read', 'Glob', 'Grep'] as const, /** Full tool access for feature implementation */ - fullAccess: ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch', 'TodoWrite'] as const, + fullAccess: [ + 'Read', + 'Write', + 'Edit', + 'Glob', + 'Grep', + 'Bash', + 'WebSearch', + 'WebFetch', + 'TodoWrite', + ] as const, /** Tools for chat/interactive mode */ - chat: ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch', 'TodoWrite'] as const, + chat: [ + 'Read', + 'Write', + 'Edit', + 'Glob', + 'Grep', + 'Bash', + 'WebSearch', + 'WebFetch', + 'TodoWrite', + ] as const, } as const; /** diff --git a/docs/docker-isolation.md b/docs/docker-isolation.md index ad7c712a..379e8c0d 100644 --- a/docs/docker-isolation.md +++ b/docs/docker-isolation.md @@ -136,12 +136,11 @@ volumes: ## Troubleshooting -| Problem | Solution | -| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| Container won't start | Check `.env` has `ANTHROPIC_API_KEY` set. Run `docker-compose logs` for errors. | -| Can't access web UI | Verify container is running with `docker ps \| grep automaker` | -| Need a fresh start | Run `docker-compose down && docker volume rm automaker-data && docker-compose up -d --build` | -| Cursor auth fails | Re-extract token with `./scripts/get-cursor-token.sh` - tokens expire periodically. Make sure you've run `cursor-agent login` on your host first. | -| OpenCode not detected | Mount `~/.local/share/opencode` to `/home/automaker/.local/share/opencode`. Make sure you've run `opencode auth login` on your host first. | +| Problem | Solution | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Container won't start | Check `.env` has `ANTHROPIC_API_KEY` set. Run `docker-compose logs` for errors. | +| Can't access web UI | Verify container is running with `docker ps \| grep automaker` | +| Need a fresh start | Run `docker-compose down && docker volume rm automaker-data && docker-compose up -d --build` | +| Cursor auth fails | Re-extract token with `./scripts/get-cursor-token.sh` - tokens expire periodically. Make sure you've run `cursor-agent login` on your host first. | +| OpenCode not detected | Mount `~/.local/share/opencode` to `/home/automaker/.local/share/opencode`. Make sure you've run `opencode auth login` on your host first. | | File permission errors | Rebuild with `UID=$(id -u) GID=$(id -g) docker-compose build` to match container user to your host user. See [Fixing File Permission Issues](#fixing-file-permission-issues). | - From 7ef525effac6b6058b3555103dde9dfe09ec71f9 Mon Sep 17 00:00:00 2001 From: Shirone Date: Tue, 13 Jan 2026 18:51:20 +0100 Subject: [PATCH 08/11] fix: clarify comments on branch name handling in BoardView Updated comments in BoardView to better explain the behavior of the 'current' work mode. The changes specify that an empty string clears the branch assignment, allowing work to proceed on the main/current branch. This enhances code readability and understanding of branch management logic. --- apps/ui/src/components/views/board-view.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/ui/src/components/views/board-view.tsx b/apps/ui/src/components/views/board-view.tsx index 8f43b677..f51357d0 100644 --- a/apps/ui/src/components/views/board-view.tsx +++ b/apps/ui/src/components/views/board-view.tsx @@ -503,14 +503,14 @@ export function BoardView() { try { // Determine final branch name based on work mode: - // - 'current': No branch name, work on current branch (no worktree) + // - 'current': Empty string to clear branch assignment (work on main/current branch) // - 'auto': Auto-generate branch name based on current branch // - 'custom': Use the provided branch name let finalBranchName: string | undefined; if (workMode === 'current') { - // No worktree isolation - work directly on current branch - finalBranchName = undefined; + // Empty string clears the branch assignment, moving features to main/current branch + finalBranchName = ''; } else if (workMode === 'auto') { // Auto-generate a branch name based on current branch and timestamp const baseBranch = From 62af2031f68914a3b2982c3e2c175de6f369f668 Mon Sep 17 00:00:00 2001 From: Shirone Date: Tue, 13 Jan 2026 19:33:09 +0100 Subject: [PATCH 09/11] feat: enhance dev server URL handling and improve accessibility - Added URL and URLSearchParams as readonly globals in ESLint configuration. - Updated WorktreeActionsDropdown and WorktreeTab components to include aria-labels for better accessibility. - Implemented error handling for dev server URL opening, ensuring only valid HTTP/HTTPS protocols are used and providing user feedback for errors. These changes improve user experience and accessibility when interacting with the dev server functionality. --- apps/ui/eslint.config.mjs | 2 + .../components/worktree-actions-dropdown.tsx | 8 +++- .../components/worktree-tab.tsx | 37 ++++++++++++------- .../worktree-panel/hooks/use-dev-servers.ts | 33 ++++++++++++++++- 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/apps/ui/eslint.config.mjs b/apps/ui/eslint.config.mjs index d7bc54d4..6db837e3 100644 --- a/apps/ui/eslint.config.mjs +++ b/apps/ui/eslint.config.mjs @@ -70,6 +70,8 @@ const eslintConfig = defineConfig([ AbortSignal: 'readonly', Audio: 'readonly', ScrollBehavior: 'readonly', + URL: 'readonly', + URLSearchParams: 'readonly', // Timers setTimeout: 'readonly', setInterval: 'readonly', diff --git a/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx b/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx index c7d8f26b..27cc38d8 100644 --- a/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx +++ b/apps/ui/src/components/views/board-view/worktree-panel/components/worktree-actions-dropdown.tsx @@ -143,8 +143,12 @@ export function WorktreeActionsDropdown({ Dev Server Running (:{devServerInfo?.port}) - onOpenDevServerUrl(worktree)} className="text-xs"> - + onOpenDevServerUrl(worktree)} + className="text-xs" + aria-label={`Open dev server on port ${devServerInfo?.port} in browser`} + > + onOpenDevServerUrl(worktree)} - title={`Open dev server (port ${devServerInfo?.port})`} - > - - + + + + + + +

Open dev server (:{devServerInfo?.port})

+
+
+
)} { const serverInfo = runningDevServers.get(getWorktreeKey(worktree)); - if (serverInfo) { - window.open(serverInfo.url, '_blank'); + if (!serverInfo) { + logger.warn('No dev server info found for worktree:', getWorktreeKey(worktree)); + toast.error('Dev server not found', { + description: 'The dev server may have stopped. Try starting it again.', + }); + return; + } + + try { + // Rewrite URL hostname to match the current browser's hostname. + // This ensures dev server URLs work when accessing Automaker from + // remote machines (e.g., 192.168.x.x or hostname.local instead of localhost). + const devServerUrl = new URL(serverInfo.url); + + // Security: Only allow http/https protocols to prevent potential attacks + // via data:, javascript:, file:, or other dangerous URL schemes + if (devServerUrl.protocol !== 'http:' && devServerUrl.protocol !== 'https:') { + logger.error('Invalid dev server URL protocol:', devServerUrl.protocol); + toast.error('Invalid dev server URL', { + description: 'The server returned an unsupported URL protocol.', + }); + return; + } + + devServerUrl.hostname = window.location.hostname; + window.open(devServerUrl.toString(), '_blank'); + } catch (error) { + logger.error('Failed to parse dev server URL:', error); + toast.error('Failed to open dev server', { + description: 'The server URL could not be processed. Please try again.', + }); } }, [runningDevServers, getWorktreeKey] From f4390bc82f598be12277f570a4e0c64d96d341b4 Mon Sep 17 00:00:00 2001 From: Shirone Date: Tue, 13 Jan 2026 19:43:20 +0100 Subject: [PATCH 10/11] security: add noopener,noreferrer to window.open calls Add 'noopener,noreferrer' parameter to all window.open() calls with target='_blank' to prevent tabnabbing attacks. This prevents the newly opened page from accessing window.opener, protecting against potential security vulnerabilities. Affected files: - use-dev-servers.ts: Dev server URL links - worktree-actions-dropdown.tsx: PR URL links - create-pr-dialog.tsx: PR creation and browser fallback links Co-Authored-By: Claude Sonnet 4.5 --- .../views/board-view/dialogs/create-pr-dialog.tsx | 11 +++++++---- .../components/worktree-actions-dropdown.tsx | 2 +- .../worktree-panel/hooks/use-dev-servers.ts | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/ui/src/components/views/board-view/dialogs/create-pr-dialog.tsx b/apps/ui/src/components/views/board-view/dialogs/create-pr-dialog.tsx index a8ba8ee5..3abbb75f 100644 --- a/apps/ui/src/components/views/board-view/dialogs/create-pr-dialog.tsx +++ b/apps/ui/src/components/views/board-view/dialogs/create-pr-dialog.tsx @@ -117,7 +117,7 @@ export function CreatePRDialog({ description: `PR already exists for ${result.result.branch}`, action: { label: 'View PR', - onClick: () => window.open(result.result!.prUrl!, '_blank'), + onClick: () => window.open(result.result!.prUrl!, '_blank', 'noopener,noreferrer'), }, }); } else { @@ -125,7 +125,7 @@ export function CreatePRDialog({ description: `PR created from ${result.result.branch}`, action: { label: 'View PR', - onClick: () => window.open(result.result!.prUrl!, '_blank'), + onClick: () => window.open(result.result!.prUrl!, '_blank', 'noopener,noreferrer'), }, }); } @@ -251,7 +251,10 @@ export function CreatePRDialog({

Your PR is ready for review

- @@ -277,7 +280,7 @@ export function CreatePRDialog({