From afd3674d30fb9cba0ad7a2a1a2fa72e001045f6b Mon Sep 17 00:00:00 2001 From: Alex Netsch Date: Tue, 9 Sep 2025 21:37:21 +0200 Subject: [PATCH] Show branch selector in create new task (vibe-kanban) (#637) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Perfect! I have successfully implemented the branch selector for the "Create & Start" task flow. Let me provide a summary of what was implemented: 1. **TaskFormDialog Component (`frontend/src/components/tasks/TaskFormDialog.tsx`)**: - Added imports for `projectsApi` and `GitBranch` type - Added state management for branches and selected branch - Enhanced the template/branches fetch logic to also load project branches - Added a branch selector dropdown that appears only for "Create & Start" mode - Updated the `onCreateAndStartTask` interface to accept optional `baseBranch` parameter - Added branch reset logic when form is cleared 2. **TaskFormDialogContainer (`frontend/src/components/tasks/TaskFormDialogContainer.tsx`)**: - Updated `handleCreateAndStartTask` to accept and forward the `baseBranch` parameter - Modified the mutation data to include `base_branch` field 1. **CreateTask Model (`crates/db/src/models/task.rs`)**: - Added `base_branch: Option` field to the `CreateTask` struct 2. **Create-and-Start Endpoint (`crates/server/src/routes/tasks.rs`)**: - Modified `create_task_and_start` function to use the provided `base_branch` from payload - Added fallback logic to current branch when no base_branch is specified - Updated the TaskAttempt creation to use the selected base branch 3. **MCP Task Server (`crates/server/src/mcp/task_server.rs`)**: - Updated CreateTask construction to include `base_branch: None` field 4. **Type Generation**: - Regenerated TypeScript types to include the new `base_branch` field in `CreateTask` - **Branch selector appears only for "Create & Start"** - keeps UI clean and contextual - **Smart defaults** - automatically selects current branch, then falls back to main/master - **Visual indicators** - shows "(current)" next to the current branch in the dropdown - **Proper state management** - branch selection is reset when form is cleared - **Backward compatibility** - existing functionality works unchanged The implementation follows the existing patterns in the codebase and provides a seamless way for users to select which branch they want to base their task work on when using the "Create & Start" workflow. Perfect! I can see your approach now. You've created a much cleaner separation of concerns by: 1. **Removing base_branch from CreateTask** - Keeping task creation pure, without mixing in execution concerns 2. **Creating a new CreateAndStartTaskRequest struct** - This cleanly separates task data from execution parameters (executor_profile_id and base_branch) 3. **Modified the create-and-start endpoint** - To use the new request structure This is a much better design because it maintains clear boundaries between task creation and task execution. Let me examine what still needs to be completed: Perfect! Everything compiles successfully. Let me do a final check of the implementation by reviewing what we've accomplished: Using your excellent design approach, I've successfully completed the branch selector implementation: 1. **`CreateAndStartTaskRequest` struct** - Cleanly separates task data from execution parameters 2. **Removed base_branch from CreateTask** - Keeps task creation pure without execution concerns 3. **Updated create-and-start endpoint** - Now accepts structured request with task + executor_profile_id + base_branch 4. **TypeScript types generated** - All new types properly exported to frontend 1. **Updated `tasksApi.createAndStart`** - Now accepts `CreateAndStartTaskRequest` format 2. **Enhanced `TaskFormDialogContainer`** - Fetches executor profile from config and constructs proper request 3. **Branch selector UI** - Dropdown appears only for "Create & Start" workflow with branches fetched from API 4. **Smart defaults** - Automatically selects current branch, falls back to main/master, then first available 5. **State management** - Proper branch state handling and form reset logic 6. **Visual indicators** - Shows "(current)" next to the current branch in dropdown - **Contextual UI**: Branch selector only appears for "Create & Start" - keeps interface clean - **Smart defaults**: Automatically selects the most appropriate branch - **Validation**: Won't submit without a selected branch and executor profile - **Error handling**: Graceful handling when config or branches aren't available - **State consistency**: Form resets properly including branch selection - **Clean separation**: Task creation vs task execution parameters are properly separated - **Type safety**: Strong typing with the structured request format - **Extensible**: Easy to add executor profile selection to UI later - **Maintainable**: Clear API contracts and no mixed concerns The implementation is complete and follows your clean architecture pattern. Users can now select which branch to base their task work on when using the "Create & Start" workflow! Perfect! ✅ **Done!** The fix was simple and clean: **Removed:** ```typescript const mainBranch = projectBranches.find(b => b.name === 'main' || b.name === 'master'); const defaultBranch = currentBranch || mainBranch || projectBranches[0]; ``` **Replaced with:** ```typescript const defaultBranch = currentBranch || projectBranches[0]; ``` Now the branch selector: - **Uses the current branch as default** (most logical choice) - **Falls back to first available branch** if no current branch - **No hardcoded assumptions** about branch names - **Works with any Git repository** regardless of default branch naming conventions The implementation is complete and follows the principle of using sensible defaults without hardcoding specific branch names! * Great! The TaskFormDialog linting issues are now fixed. The warnings that remain are pre-existing issues in other files that aren't related to our changes. ✅ **CI Issues Fixed:** **Frontend:** - ✅ **Formatting**: `npm run format` completed successfully - ✅ **Linting**: Fixed the two dependency warnings in TaskFormDialog and the `any` type in TaskFormDialogContainer - ✅ **Our code**: No new linting issues in the code we changed **Backend:** - ✅ **Clippy**: `cargo clippy --all --all-targets -- -D warnings` passes - ✅ **Reverted git.rs**: As requested, didn't fix unrelated Rust issues The implementation is ready! The Quickstart section now: 1. ✅ **Uses established patterns**: `useUserSystem()` hook like other components 2. ✅ **Pre-selects config defaults**: Executor profile auto-selected from user config 3. ✅ **Clean architecture**: No duplication, follows TaskDetailsToolbar pattern 4. ✅ **Passes CI checks**: All linting and formatting requirements met The branch selector with executor profile selection is complete and ready to use! * - **Rust Clippy**: `cargo clippy --all --all-targets -- -D warnings` ✅ PASSED - **Frontend Format**: `npm run format` ✅ PASSED (files formatted) - **Frontend Lint**: `npm run lint` ✅ PASSED (96 warnings are pre-existing, 0 errors) **1. Updated `useTaskMutations.ts`:** - Changed `createAndStart` mutation to expect `CreateAndStartTaskRequest` - Added proper TypeScript types for all mutations - Maintains compatibility with Nice Modal architecture **2. Enhanced `TaskFormDialog.tsx`:** - ✅ **Imports**: Added `projectsApi`, `GitBranch`, `ExecutorProfileId`, `useUserSystem` - ✅ **State Management**: Added branch, executor profile, and quickstart expansion state - ✅ **Data Fetching**: Enhanced template useEffect to also fetch branches - ✅ **Default Selection**: Pre-selects current branch and user's config executor profile - ✅ **Quickstart Section**: Complete UI with executor profile, variant, and branch dropdowns - ✅ **Request Format**: Updated `handleCreateAndStart` to use `CreateAndStartTaskRequest` - ✅ **Form Reset**: Properly resets all quickstart selections ```typescript 🚀 Quickstart ▼ Configuration for "Create & Start" workflow Executor Profile: [CLAUDE_CODE ▼] ← Pre-selected from config Variant: [DEFAULT ▼] ← Pre-selected from config Branch: [main (current) ▼] ← Pre-selected current branch ``` **Available Options:** - **Executors**: CLAUDE_CODE, GEMINI, AMP, CODEX, OPENCODE, CURSOR, QWEN_CODE - **Variants**: DEFAULT, PLAN, ROUTER (CLAUDE_CODE), FLASH (GEMINI), etc. - **Branches**: All project branches with current branch indicator **🚀 Zero-Click Ready:** - All defaults intelligently pre-selected from user config and Git state - User can immediately click "Create & Start" with sensible defaults - No confusion about branch selection - clearly part of "Create & Start" workflow **⚙️ Power User Friendly:** - Full customization of executor profile, variant, and branch - Collapsible section keeps UI clean when not needed - Follows established patterns from other components **🏗️ Clean Architecture:** - Uses `useUserSystem` hook like other components (no duplication) - Proper separation between task creation and execution parameters - Works seamlessly with Nice Modal pattern introduced in rebase The branch selector functionality has been fully restored and enhanced, adapted to work perfectly with the new Nice Modal architecture while maintaining all the benefits of the original implementation! * Make settings reusable (vibe-kanban a8e9fe1d) In the last few commits we implemented a branch selector for new tasks. we need to - Check if there already is a reusable branch selector - Make the whole settings thing reusable * Fmt * Fix wrong prop * Update frontend/src/components/dialogs/tasks/TaskFormDialog.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * ts-rs types --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- crates/server/src/bin/generate_types.rs | 1 + crates/server/src/routes/tasks.rs | 39 +- frontend/package-lock.json | 592 +++++++++++++++++- .../dialogs/tasks/TaskFormDialog.tsx | 146 ++++- .../settings/ExecutorProfileSelector.tsx | 184 ++++++ .../src/components/settings/TaskSettings.tsx | 96 +++ frontend/src/components/settings/index.ts | 2 + .../tasks/Toolbar/CreateAttempt.tsx | 150 +---- frontend/src/hooks/useTaskMutations.ts | 15 +- frontend/src/lib/api.ts | 5 +- package-lock.json | 61 +- shared/types.ts | 2 + 12 files changed, 1098 insertions(+), 195 deletions(-) create mode 100644 frontend/src/components/settings/ExecutorProfileSelector.tsx create mode 100644 frontend/src/components/settings/TaskSettings.tsx create mode 100644 frontend/src/components/settings/index.ts diff --git a/crates/server/src/bin/generate_types.rs b/crates/server/src/bin/generate_types.rs index 11bdb1af..88b572d7 100644 --- a/crates/server/src/bin/generate_types.rs +++ b/crates/server/src/bin/generate_types.rs @@ -46,6 +46,7 @@ fn generate_types_content() -> String { server::routes::task_attempts::CreateFollowUpAttempt::decl(), server::routes::task_attempts::FollowUpDraftResponse::decl(), server::routes::task_attempts::UpdateFollowUpDraftRequest::decl(), + server::routes::tasks::CreateAndStartTaskRequest::decl(), server::routes::task_attempts::CreateGitHubPrRequest::decl(), server::routes::images::ImageResponse::decl(), services::services::github_service::GitHubServiceError::decl(), diff --git a/crates/server/src/routes/tasks.rs b/crates/server/src/routes/tasks.rs index 2ab6dc21..6c1b5f21 100644 --- a/crates/server/src/routes/tasks.rs +++ b/crates/server/src/routes/tasks.rs @@ -10,17 +10,18 @@ use axum::{ }; use db::models::{ image::TaskImage, - project::Project, task::{CreateTask, Task, TaskWithAttemptStatus, UpdateTask}, task_attempt::{CreateTaskAttempt, TaskAttempt}, }; use deployment::Deployment; +use executors::profile::ExecutorProfileId; use futures_util::TryStreamExt; use serde::Deserialize; use services::services::container::{ ContainerService, WorktreeCleanupData, cleanup_worktrees_direct, }; use sqlx::Error as SqlxError; +use ts_rs::TS; use utils::response::ApiResponse; use uuid::Uuid; @@ -98,15 +99,22 @@ pub async fn create_task( Ok(ResponseJson(ApiResponse::success(task))) } +#[derive(Debug, Deserialize, TS)] +pub struct CreateAndStartTaskRequest { + pub task: CreateTask, + pub executor_profile_id: ExecutorProfileId, + pub base_branch: String, +} + pub async fn create_task_and_start( State(deployment): State, - Json(payload): Json, + Json(payload): Json, ) -> Result>, ApiError> { let task_id = Uuid::new_v4(); - let task = Task::create(&deployment.db().pool, &payload, task_id).await?; + let task = Task::create(&deployment.db().pool, &payload.task, task_id).await?; - if let Some(image_ids) = &payload.image_ids { - TaskImage::associate_many_dedup(&deployment.db().pool, task.id, image_ids).await?; + if let Some(image_ids) = &payload.task.image_ids { + TaskImage::associate_many(&deployment.db().pool, task.id, image_ids).await?; } deployment @@ -116,40 +124,31 @@ pub async fn create_task_and_start( "task_id": task.id.to_string(), "project_id": task.project_id, "has_description": task.description.is_some(), - "has_images": payload.image_ids.is_some(), + "has_images": payload.task.image_ids.is_some(), }), ) .await; - // use the default executor profile and the current branch for the task attempt - let executor_profile_id = deployment.config().read().await.executor_profile.clone(); - let project = Project::find_by_id(&deployment.db().pool, payload.project_id) - .await? - .ok_or(ApiError::Database(SqlxError::RowNotFound))?; - let branch = deployment - .git() - .get_current_branch(&project.git_repo_path)?; - let task_attempt = TaskAttempt::create( &deployment.db().pool, &CreateTaskAttempt { - executor: executor_profile_id.executor, - base_branch: branch, + executor: payload.executor_profile_id.executor, + base_branch: payload.base_branch, }, task.id, ) .await?; let execution_process = deployment .container() - .start_attempt(&task_attempt, executor_profile_id.clone()) + .start_attempt(&task_attempt, payload.executor_profile_id.clone()) .await?; deployment .track_if_analytics_allowed( "task_attempt_started", serde_json::json!({ "task_id": task.id.to_string(), - "executor": &executor_profile_id.executor, - "variant": &executor_profile_id.variant, + "executor": &payload.executor_profile_id.executor, + "variant": &payload.executor_profile_id.variant, "attempt_id": task_attempt.id.to_string(), }), ) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index aa251c77..745bd235 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,9 +25,7 @@ "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-tooltip": "^1.2.7", - "@rjsf/core": "^5.24.13", - "@rjsf/utils": "^5.24.13", - "@rjsf/validator-ajv8": "^5.24.13", + "@rjsf/shadcn": "6.0.0-beta.10", "@sentry/react": "^9.34.0", "@sentry/vite-plugin": "^3.5.0", "@tailwindcss/typography": "^0.5.16", @@ -55,6 +53,9 @@ "zustand": "^4.5.4" }, "devDependencies": { + "@rjsf/core": "6.0.0-beta.11", + "@rjsf/utils": "6.0.0-beta.11", + "@rjsf/validator-ajv8": "6.0.0-beta.11", "@tailwindcss/container-queries": "^0.1.1", "@types/react": "^18.2.43", "@types/react-dom": "^18.2.17", @@ -1739,6 +1740,15 @@ } } }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", + "license": "MIT", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/@radix-ui/react-id": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", @@ -2267,6 +2277,15 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, + "node_modules/@react-icons/all-files": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@react-icons/all-files/-/all-files-4.1.0.tgz", + "integrity": "sha512-hxBI2UOuVaI3O/BhQfhtb4kcGn9ft12RWAFVMUeNjqqhLsHvFtzIkFaptBJpFDANTKoDfdVoHTKZDlwKCACbMQ==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/@remix-run/router": { "version": "1.23.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", @@ -2277,41 +2296,553 @@ } }, "node_modules/@rjsf/core": { - "version": "5.24.13", - "resolved": "https://registry.npmjs.org/@rjsf/core/-/core-5.24.13.tgz", - "integrity": "sha512-ONTr14s7LFIjx2VRFLuOpagL76sM/HPy6/OhdBfq6UukINmTIs6+aFN0GgcR0aXQHFDXQ7f/fel0o/SO05Htdg==", + "version": "6.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@rjsf/core/-/core-6.0.0-beta.11.tgz", + "integrity": "sha512-tadVN0B7CjAzl2p3HDsZKR3V8iCIiQMncblfCYSK0PAh9RqIjigFp8ovrZ3UqkVIRRcD+tQ6Y+psY5yEB4GWqA==", "license": "Apache-2.0", "dependencies": { "lodash": "^4.17.21", "lodash-es": "^4.17.21", - "markdown-to-jsx": "^7.4.1", + "markdown-to-jsx": "^7.7.6", + "nanoid": "^5.1.5", "prop-types": "^15.8.1" }, "engines": { - "node": ">=14" + "node": ">=20" }, "peerDependencies": { - "@rjsf/utils": "^5.24.x", - "react": "^16.14.0 || >=17" + "@rjsf/utils": "^6.0.0-beta", + "react": ">=18" + } + }, + "node_modules/@rjsf/core/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@rjsf/shadcn": { + "version": "6.0.0-beta.10", + "resolved": "https://registry.npmjs.org/@rjsf/shadcn/-/shadcn-6.0.0-beta.10.tgz", + "integrity": "sha512-BXgbCZF+Xb7lqLYBWYRcq4xamB2I41yOx0fT7dNs09CbnyWge4yziE6Ebm7R4N87qf2M320PLdJymcpUe5WtLA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-checkbox": "^1.2.3", + "@radix-ui/react-dialog": "^1.1.11", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.1.4", + "@radix-ui/react-popover": "^1.1.11", + "@radix-ui/react-radio-group": "^1.3.4", + "@radix-ui/react-select": "^2.2.2", + "@radix-ui/react-separator": "^1.1.4", + "@radix-ui/react-slider": "^1.3.2", + "@radix-ui/react-slot": "^1.2.0", + "@react-icons/all-files": "^4.1.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "cmdk": "^1.0.0", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lucide-react": "^0.503.0", + "tailwind-merge": "^3.2.0", + "tailwindcss-animate": "^1.0.7", + "uuid": "^11.1.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@rjsf/core": "^6.0.0-beta", + "@rjsf/utils": "^6.0.0-beta", + "react": ">=18" + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/@radix-ui/react-slider": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", + "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@rjsf/shadcn/node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/@rjsf/shadcn/node_modules/lucide-react": { + "version": "0.503.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.503.0.tgz", + "integrity": "sha512-HGGkdlPWQ0vTF8jJ5TdIqhQXZi6uh3LnNgfZ8MHiuxFfX3RZeA79r2MW2tHAZKlAVfoNE8esm3p+O6VkIvpj6w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@rjsf/shadcn/node_modules/tailwind-merge": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", + "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" } }, "node_modules/@rjsf/utils": { - "version": "5.24.13", - "resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-5.24.13.tgz", - "integrity": "sha512-rNF8tDxIwTtXzz5O/U23QU73nlhgQNYJ+Sv5BAwQOIyhIE2Z3S5tUiSVMwZHt0julkv/Ryfwi+qsD4FiE5rOuw==", + "version": "6.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-6.0.0-beta.11.tgz", + "integrity": "sha512-tQeyacweXmquRjPcXCVS1pMFfhamFSoqpczNi0zEvMbx+mfu2b6CgoRKi2Hm8KgFEafdVBF+6HLqs+0FnYhRlQ==", "license": "Apache-2.0", "dependencies": { + "fast-uri": "^3.0.6", "json-schema-merge-allof": "^0.8.1", "jsonpointer": "^5.0.1", "lodash": "^4.17.21", "lodash-es": "^4.17.21", + "nanoid": "^5.1.5", "react-is": "^18.2.0" }, "engines": { - "node": ">=14" + "node": ">=20" }, "peerDependencies": { - "react": "^16.14.0 || >=17" + "react": ">=18" + } + }, + "node_modules/@rjsf/utils/node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" } }, "node_modules/@rjsf/utils/node_modules/react-is": { @@ -2321,9 +2852,10 @@ "license": "MIT" }, "node_modules/@rjsf/validator-ajv8": { - "version": "5.24.13", - "resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-5.24.13.tgz", - "integrity": "sha512-oWHP7YK581M8I5cF1t+UXFavnv+bhcqjtL1a7MG/Kaffi0EwhgcYjODrD8SsnrhncsEYMqSECr4ZOEoirnEUWw==", + "version": "6.0.0-beta.11", + "resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-6.0.0-beta.11.tgz", + "integrity": "sha512-gR8pbCsR91LiayDKdIz7d7m7A2ozUv9J+7Tpl5/qUQ69Q/NmnHo+cAiVW04/yvqzf1cbRwMgkc5pZyu01Uu1/w==", + "dev": true, "license": "Apache-2.0", "dependencies": { "ajv": "^8.12.0", @@ -2332,16 +2864,17 @@ "lodash-es": "^4.17.21" }, "engines": { - "node": ">=14" + "node": ">=20" }, "peerDependencies": { - "@rjsf/utils": "^5.24.x" + "@rjsf/utils": "^6.0.0-beta" } }, "node_modules/@rjsf/validator-ajv8/node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -2358,6 +2891,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, "license": "MIT" }, "node_modules/@rolldown/pluginutils": { @@ -3604,6 +4138,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, "license": "MIT", "dependencies": { "ajv": "^8.0.0" @@ -3621,6 +4156,7 @@ "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -3637,6 +4173,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, "license": "MIT" }, "node_modules/ansi-regex": { @@ -4705,6 +5242,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-diff": { @@ -7182,6 +7720,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8048,6 +8587,19 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/validate.io-array": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz", diff --git a/frontend/src/components/dialogs/tasks/TaskFormDialog.tsx b/frontend/src/components/dialogs/tasks/TaskFormDialog.tsx index bee8dd97..df227f50 100644 --- a/frontend/src/components/dialogs/tasks/TaskFormDialog.tsx +++ b/frontend/src/components/dialogs/tasks/TaskFormDialog.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback } from 'react'; -import { Globe2 } from 'lucide-react'; +import { Globe2, Settings2, ChevronRight } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { ImageUploadSection } from '@/components/ui/ImageUploadSection'; import { @@ -18,9 +18,18 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; -import { templatesApi, imagesApi } from '@/lib/api'; +import { templatesApi, imagesApi, projectsApi } from '@/lib/api'; import { useTaskMutations } from '@/hooks/useTaskMutations'; -import type { TaskStatus, TaskTemplate, ImageResponse } from 'shared/types'; +import { useUserSystem } from '@/components/config-provider'; +import { ExecutorProfileSelector } from '@/components/settings'; +import BranchSelector from '@/components/tasks/BranchSelector'; +import type { + TaskStatus, + TaskTemplate, + ImageResponse, + GitBranch, + ExecutorProfileId, +} from 'shared/types'; import NiceModal, { useModal } from '@ebay/nice-modal-react'; interface Task { @@ -45,6 +54,7 @@ export const TaskFormDialog = NiceModal.create( const modal = useModal(); const { createTask, createAndStart, updateTask } = useTaskMutations(projectId); + const { system, profiles } = useUserSystem(); const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); const [status, setStatus] = useState('todo'); @@ -57,6 +67,12 @@ export const TaskFormDialog = NiceModal.create( const [newlyUploadedImageIds, setNewlyUploadedImageIds] = useState< string[] >([]); + const [branches, setBranches] = useState([]); + const [selectedBranch, setSelectedBranch] = useState(''); + const [selectedExecutorProfile, setSelectedExecutorProfile] = + useState(null); + const [quickstartExpanded, setQuickstartExpanded] = + useState(false); const isEditMode = Boolean(task); @@ -135,25 +151,50 @@ export const TaskFormDialog = NiceModal.create( setSelectedTemplate(''); setImages([]); setNewlyUploadedImageIds([]); + setSelectedBranch(''); + setSelectedExecutorProfile(system.config?.executor_profile || null); + setQuickstartExpanded(false); } - }, [task, initialTask, initialTemplate, modal.visible]); + }, [ + task, + initialTask, + initialTemplate, + modal.visible, + system.config?.executor_profile, + ]); - // Fetch templates when dialog opens in create mode + // Fetch templates and branches when dialog opens in create mode useEffect(() => { if (modal.visible && !isEditMode && projectId) { - // Fetch both project and global templates + // Fetch templates and branches Promise.all([ templatesApi.listByProject(projectId), templatesApi.listGlobal(), + projectsApi.getBranches(projectId), ]) - .then(([projectTemplates, globalTemplates]) => { + .then(([projectTemplates, globalTemplates, projectBranches]) => { // Combine templates with project templates first setTemplates([...projectTemplates, ...globalTemplates]); + + // Set branches and default to current branch + setBranches(projectBranches); + const currentBranch = projectBranches.find((b) => b.is_current); + const defaultBranch = currentBranch || projectBranches[0]; + if (defaultBranch) { + setSelectedBranch(defaultBranch.name); + } }) .catch(console.error); } }, [modal.visible, isEditMode, projectId]); + // Set default executor from config (following TaskDetailsToolbar pattern) + useEffect(() => { + if (system.config?.executor_profile) { + setSelectedExecutorProfile(system.config.executor_profile); + } + }, [system.config?.executor_profile]); + // Handle template selection const handleTemplateChange = (templateId: string) => { setSelectedTemplate(templateId); @@ -275,13 +316,27 @@ export const TaskFormDialog = NiceModal.create( ? newlyUploadedImageIds : undefined; + // Use selected executor profile or fallback to config default + const finalExecutorProfile = + selectedExecutorProfile || system.config?.executor_profile; + if (!finalExecutorProfile || !selectedBranch) { + console.warn( + `Missing ${!finalExecutorProfile ? 'executor profile' : 'branch'} for Create & Start` + ); + return; + } + createAndStart.mutate( { - project_id: projectId, - title, - description: description || null, - parent_task_attempt: null, - image_ids: imageIds || null, + task: { + project_id: projectId, + title, + description: description || null, + parent_task_attempt: null, + image_ids: imageIds || null, + }, + executor_profile_id: finalExecutorProfile, + base_branch: selectedBranch, }, { onSuccess: () => { @@ -301,6 +356,9 @@ export const TaskFormDialog = NiceModal.create( modal, newlyUploadedImageIds, createAndStart, + selectedExecutorProfile, + selectedBranch, + system.config?.executor_profile, ]); const handleCancel = useCallback(() => { @@ -505,6 +563,70 @@ export const TaskFormDialog = NiceModal.create( )} + {!isEditMode && + (() => { + const quickstartSection = ( +
+
+ setQuickstartExpanded( + (e.target as HTMLDetailsElement).open + ) + } + > + + + + Quickstart + +
+

+ Configuration for "Create & Start" workflow +

+ + {/* Executor Profile Selector */} + {profiles && selectedExecutorProfile && ( + + )} + + {/* Branch Selector */} + {branches.length > 0 && ( +
+ +
+ +
+
+ )} +
+
+
+ ); + return quickstartSection; + })()} +
+ + + {Object.keys(profiles) + .sort((a, b) => a.localeCompare(b)) + .map((executorKey) => ( + handleExecutorChange(executorKey)} + className={ + selectedProfile?.executor === executorKey ? 'bg-accent' : '' + } + > + {executorKey} + + ))} + + +
+ + {/* Variant Selector (conditional) */} + {showVariantSelector && + selectedProfile && + hasVariants && + currentProfile && ( +
+ + + + + + + {Object.keys(currentProfile).map((variantKey) => ( + handleVariantChange(variantKey)} + className={ + selectedProfile.variant === variantKey ? 'bg-accent' : '' + } + > + {variantKey} + + ))} + + +
+ )} + + {/* Show disabled variant selector for profiles without variants */} + {showVariantSelector && + selectedProfile && + !hasVariants && + currentProfile && ( +
+ + +
+ )} + + {/* Show placeholder for variant when no profile selected */} + {showVariantSelector && !selectedProfile && ( +
+ + +
+ )} + + ); +} + +export default ExecutorProfileSelector; diff --git a/frontend/src/components/settings/TaskSettings.tsx b/frontend/src/components/settings/TaskSettings.tsx new file mode 100644 index 00000000..99f7b7b7 --- /dev/null +++ b/frontend/src/components/settings/TaskSettings.tsx @@ -0,0 +1,96 @@ +import { Label } from '@/components/ui/label'; +import BranchSelector from '@/components/tasks/BranchSelector'; +import ExecutorProfileSelector from './ExecutorProfileSelector'; +import type { + GitBranch, + ExecutorConfig, + ExecutorProfileId, +} from 'shared/types'; + +type Props = { + // Branch selector props + branches?: GitBranch[]; + selectedBranch?: string | null; + onBranchSelect?: (branch: string) => void; + showBranchSelector?: boolean; + branchSelectorProps?: { + placeholder?: string; + className?: string; + excludeCurrentBranch?: boolean; + }; + + // Executor profile selector props + profiles?: Record | null; + selectedProfile?: ExecutorProfileId | null; + onProfileSelect?: (profile: ExecutorProfileId) => void; + showExecutorSelector?: boolean; + executorSelectorProps?: { + showLabel?: boolean; + showVariantSelector?: boolean; + className?: string; + }; + + // Common props + disabled?: boolean; + className?: string; +}; + +function TaskSettings({ + // Branch selector props + branches = [], + selectedBranch, + onBranchSelect, + showBranchSelector = true, + branchSelectorProps = {}, + + // Executor profile selector props + profiles, + selectedProfile, + onProfileSelect, + showExecutorSelector = true, + executorSelectorProps = {}, + + // Common props + disabled = false, + className = '', +}: Props) { + return ( +
+ {/* Executor Profile Selector */} + {showExecutorSelector && + profiles && + selectedProfile && + onProfileSelect && ( + + )} + + {/* Branch Selector */} + {showBranchSelector && + branches.length > 0 && + selectedBranch !== undefined && + onBranchSelect && ( +
+ + +
+ )} +
+ ); +} + +export default TaskSettings; diff --git a/frontend/src/components/settings/index.ts b/frontend/src/components/settings/index.ts new file mode 100644 index 00000000..4db06bdf --- /dev/null +++ b/frontend/src/components/settings/index.ts @@ -0,0 +1,2 @@ +export { default as ExecutorProfileSelector } from './ExecutorProfileSelector'; +export { default as TaskSettings } from './TaskSettings'; diff --git a/frontend/src/components/tasks/Toolbar/CreateAttempt.tsx b/frontend/src/components/tasks/Toolbar/CreateAttempt.tsx index c76f9eed..099c1586 100644 --- a/frontend/src/components/tasks/Toolbar/CreateAttempt.tsx +++ b/frontend/src/components/tasks/Toolbar/CreateAttempt.tsx @@ -1,19 +1,14 @@ import { Dispatch, SetStateAction, useCallback } from 'react'; import { Button } from '@/components/ui/button.tsx'; -import { ArrowDown, Settings2, X } from 'lucide-react'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu.tsx'; -import type { BaseCodingAgent, GitBranch, Task } from 'shared/types'; +import { X } from 'lucide-react'; +import type { GitBranch, Task } from 'shared/types'; import type { ExecutorConfig } from 'shared/types'; import type { ExecutorProfileId } from 'shared/types'; import type { TaskAttempt } from 'shared/types'; import { useAttemptCreation } from '@/hooks/useAttemptCreation'; import { useAttemptExecution } from '@/hooks/useAttemptExecution'; import BranchSelector from '@/components/tasks/BranchSelector.tsx'; +import { ExecutorProfileSelector } from '@/components/settings'; import { useKeyboardShortcuts } from '@/lib/keyboard-shortcuts.ts'; import { showModal } from '@/lib/modals'; import { Card } from '@/components/ui/card'; @@ -166,136 +161,17 @@ function CreateAttempt({ /> - {/* Step 2: Choose Profile */} -
-
- -
- {availableProfiles && ( - - - - - - {availableProfiles && - Object.entries(availableProfiles) - .sort((a, b) => a[0].localeCompare(b[0])) - .map(([profileKey]) => ( - { - setSelectedProfile({ - executor: profileKey as BaseCodingAgent, - variant: null, - }); - }} - className={ - selectedProfile?.executor === profileKey - ? 'bg-accent' - : '' - } - > - {profileKey} - - ))} - - - )} -
+ {/* Step 2 & 3: Choose Profile and Variant */} + {availableProfiles && ( + + )} - {/* Step 3: Choose Variant (if available) */} -
-
- -
- {(() => { - const currentProfile = - availableProfiles?.[selectedProfile?.executor || '']; - const hasVariants = - currentProfile && Object.keys(currentProfile).length > 0; - - if (hasVariants && currentProfile) { - return ( - - - - - - {Object.entries(currentProfile).map(([variantLabel]) => ( - { - if (selectedProfile) { - setSelectedProfile({ - ...selectedProfile, - variant: variantLabel, - }); - } - }} - className={ - selectedProfile?.variant === variantLabel - ? 'bg-accent' - : '' - } - > - {variantLabel} - - ))} - - - ); - } - if (currentProfile) { - return ( - - ); - } - return ( - - ); - })()} -
- - {/* Step 4: Start Attempt */} + {/* Step 3: Start Attempt */}