diff --git a/crates/db/.sqlx/query-72509d252c39fce77520aa816cb2acbc1fb35dc2605e7be893610599b2427f2e.json b/crates/db/.sqlx/query-283a8ef6493346c9ee3bf649e977849eb361d801cdfc8180a8f082269a6bd649.json similarity index 69% rename from crates/db/.sqlx/query-72509d252c39fce77520aa816cb2acbc1fb35dc2605e7be893610599b2427f2e.json rename to crates/db/.sqlx/query-283a8ef6493346c9ee3bf649e977849eb361d801cdfc8180a8f082269a6bd649.json index d68bd414..e3fc3b31 100644 --- a/crates/db/.sqlx/query-72509d252c39fce77520aa816cb2acbc1fb35dc2605e7be893610599b2427f2e.json +++ b/crates/db/.sqlx/query-283a8ef6493346c9ee3bf649e977849eb361d801cdfc8180a8f082269a6bd649.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "UPDATE projects SET name = $2, git_repo_path = $3, setup_script = $4, dev_script = $5, cleanup_script = $6 WHERE id = $1 RETURNING id as \"id!: Uuid\", name, git_repo_path, setup_script, dev_script, cleanup_script, created_at as \"created_at!: DateTime\", updated_at as \"updated_at!: DateTime\"", + "query": "UPDATE projects SET name = $2, git_repo_path = $3, setup_script = $4, dev_script = $5, cleanup_script = $6, copy_files = $7 WHERE id = $1 RETURNING id as \"id!: Uuid\", name, git_repo_path, setup_script, dev_script, cleanup_script, copy_files, created_at as \"created_at!: DateTime\", updated_at as \"updated_at!: DateTime\"", "describe": { "columns": [ { @@ -34,18 +34,23 @@ "type_info": "Text" }, { - "name": "created_at!: DateTime", + "name": "copy_files", "ordinal": 6, "type_info": "Text" }, { - "name": "updated_at!: DateTime", + "name": "created_at!: DateTime", "ordinal": 7, "type_info": "Text" + }, + { + "name": "updated_at!: DateTime", + "ordinal": 8, + "type_info": "Text" } ], "parameters": { - "Right": 6 + "Right": 7 }, "nullable": [ true, @@ -54,9 +59,10 @@ true, true, true, + true, false, false ] }, - "hash": "72509d252c39fce77520aa816cb2acbc1fb35dc2605e7be893610599b2427f2e" + "hash": "283a8ef6493346c9ee3bf649e977849eb361d801cdfc8180a8f082269a6bd649" } diff --git a/crates/db/.sqlx/query-90fd607fcb2dca72239ff25e618e21e174b195991eaa33722cbf5f76da84cfab.json b/crates/db/.sqlx/query-59d178b298ba60d490a9081a40064a5acb06fecbc0b164c0de2fe502d02b13a7.json similarity index 69% rename from crates/db/.sqlx/query-90fd607fcb2dca72239ff25e618e21e174b195991eaa33722cbf5f76da84cfab.json rename to crates/db/.sqlx/query-59d178b298ba60d490a9081a40064a5acb06fecbc0b164c0de2fe502d02b13a7.json index b06544b5..4b2a935c 100644 --- a/crates/db/.sqlx/query-90fd607fcb2dca72239ff25e618e21e174b195991eaa33722cbf5f76da84cfab.json +++ b/crates/db/.sqlx/query-59d178b298ba60d490a9081a40064a5acb06fecbc0b164c0de2fe502d02b13a7.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "INSERT INTO projects (id, name, git_repo_path, setup_script, dev_script, cleanup_script) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id as \"id!: Uuid\", name, git_repo_path, setup_script, dev_script, cleanup_script, created_at as \"created_at!: DateTime\", updated_at as \"updated_at!: DateTime\"", + "query": "INSERT INTO projects (id, name, git_repo_path, setup_script, dev_script, cleanup_script, copy_files) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id as \"id!: Uuid\", name, git_repo_path, setup_script, dev_script, cleanup_script, copy_files, created_at as \"created_at!: DateTime\", updated_at as \"updated_at!: DateTime\"", "describe": { "columns": [ { @@ -34,18 +34,23 @@ "type_info": "Text" }, { - "name": "created_at!: DateTime", + "name": "copy_files", "ordinal": 6, "type_info": "Text" }, { - "name": "updated_at!: DateTime", + "name": "created_at!: DateTime", "ordinal": 7, "type_info": "Text" + }, + { + "name": "updated_at!: DateTime", + "ordinal": 8, + "type_info": "Text" } ], "parameters": { - "Right": 6 + "Right": 7 }, "nullable": [ true, @@ -54,9 +59,10 @@ true, true, true, + true, false, false ] }, - "hash": "90fd607fcb2dca72239ff25e618e21e174b195991eaa33722cbf5f76da84cfab" + "hash": "59d178b298ba60d490a9081a40064a5acb06fecbc0b164c0de2fe502d02b13a7" } diff --git a/crates/db/.sqlx/query-3d0a1cabf2a52e9d90cdfd29c509ca89aeb448d0c1d2446c65cd43db40735e86.json b/crates/db/.sqlx/query-71c7befa63391ca211eb69036ff0e4aabe92932fd8bb7ba8c52b2ae8bf411ac8.json similarity index 75% rename from crates/db/.sqlx/query-3d0a1cabf2a52e9d90cdfd29c509ca89aeb448d0c1d2446c65cd43db40735e86.json rename to crates/db/.sqlx/query-71c7befa63391ca211eb69036ff0e4aabe92932fd8bb7ba8c52b2ae8bf411ac8.json index 1ca98c41..6dec9ab5 100644 --- a/crates/db/.sqlx/query-3d0a1cabf2a52e9d90cdfd29c509ca89aeb448d0c1d2446c65cd43db40735e86.json +++ b/crates/db/.sqlx/query-71c7befa63391ca211eb69036ff0e4aabe92932fd8bb7ba8c52b2ae8bf411ac8.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT id as \"id!: Uuid\", name, git_repo_path, setup_script, dev_script, cleanup_script, created_at as \"created_at!: DateTime\", updated_at as \"updated_at!: DateTime\" FROM projects WHERE id = $1", + "query": "SELECT id as \"id!: Uuid\", name, git_repo_path, setup_script, dev_script, cleanup_script, copy_files, created_at as \"created_at!: DateTime\", updated_at as \"updated_at!: DateTime\" FROM projects WHERE git_repo_path = $1", "describe": { "columns": [ { @@ -34,14 +34,19 @@ "type_info": "Text" }, { - "name": "created_at!: DateTime", + "name": "copy_files", "ordinal": 6, "type_info": "Text" }, { - "name": "updated_at!: DateTime", + "name": "created_at!: DateTime", "ordinal": 7, "type_info": "Text" + }, + { + "name": "updated_at!: DateTime", + "ordinal": 8, + "type_info": "Text" } ], "parameters": { @@ -54,9 +59,10 @@ true, true, true, + true, false, false ] }, - "hash": "3d0a1cabf2a52e9d90cdfd29c509ca89aeb448d0c1d2446c65cd43db40735e86" + "hash": "71c7befa63391ca211eb69036ff0e4aabe92932fd8bb7ba8c52b2ae8bf411ac8" } diff --git a/crates/db/.sqlx/query-b2b2c6b4d0b1a347b5c4cb63c3a46a265d4db53be9554989a814b069d0af82f2.json b/crates/db/.sqlx/query-72769cc30de13bb250687b26609ee95660cb4b716615406ecb6f45c4562c3f97.json similarity index 75% rename from crates/db/.sqlx/query-b2b2c6b4d0b1a347b5c4cb63c3a46a265d4db53be9554989a814b069d0af82f2.json rename to crates/db/.sqlx/query-72769cc30de13bb250687b26609ee95660cb4b716615406ecb6f45c4562c3f97.json index 84cbfd22..32a0cbca 100644 --- a/crates/db/.sqlx/query-b2b2c6b4d0b1a347b5c4cb63c3a46a265d4db53be9554989a814b069d0af82f2.json +++ b/crates/db/.sqlx/query-72769cc30de13bb250687b26609ee95660cb4b716615406ecb6f45c4562c3f97.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT id as \"id!: Uuid\", name, git_repo_path, setup_script, dev_script, cleanup_script, created_at as \"created_at!: DateTime\", updated_at as \"updated_at!: DateTime\" FROM projects ORDER BY created_at DESC", + "query": "SELECT id as \"id!: Uuid\", name, git_repo_path, setup_script, dev_script, cleanup_script, copy_files, created_at as \"created_at!: DateTime\", updated_at as \"updated_at!: DateTime\" FROM projects ORDER BY created_at DESC", "describe": { "columns": [ { @@ -34,14 +34,19 @@ "type_info": "Text" }, { - "name": "created_at!: DateTime", + "name": "copy_files", "ordinal": 6, "type_info": "Text" }, { - "name": "updated_at!: DateTime", + "name": "created_at!: DateTime", "ordinal": 7, "type_info": "Text" + }, + { + "name": "updated_at!: DateTime", + "ordinal": 8, + "type_info": "Text" } ], "parameters": { @@ -54,9 +59,10 @@ true, true, true, + true, false, false ] }, - "hash": "b2b2c6b4d0b1a347b5c4cb63c3a46a265d4db53be9554989a814b069d0af82f2" + "hash": "72769cc30de13bb250687b26609ee95660cb4b716615406ecb6f45c4562c3f97" } diff --git a/crates/db/.sqlx/query-3a5b3c98a55ca183ab20c74708e3d7e579dda37972c059e7515c4ceee4bd8dd3.json b/crates/db/.sqlx/query-821192d8d8a8fba8ce0f144a32e7e500aaa2b6e527b7e7f082a1c73b1f9f9eb8.json similarity index 75% rename from crates/db/.sqlx/query-3a5b3c98a55ca183ab20c74708e3d7e579dda37972c059e7515c4ceee4bd8dd3.json rename to crates/db/.sqlx/query-821192d8d8a8fba8ce0f144a32e7e500aaa2b6e527b7e7f082a1c73b1f9f9eb8.json index a0dfb371..d3b1aad8 100644 --- a/crates/db/.sqlx/query-3a5b3c98a55ca183ab20c74708e3d7e579dda37972c059e7515c4ceee4bd8dd3.json +++ b/crates/db/.sqlx/query-821192d8d8a8fba8ce0f144a32e7e500aaa2b6e527b7e7f082a1c73b1f9f9eb8.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT id as \"id!: Uuid\", name, git_repo_path, setup_script, dev_script, cleanup_script, created_at as \"created_at!: DateTime\", updated_at as \"updated_at!: DateTime\" FROM projects WHERE git_repo_path = $1", + "query": "SELECT id as \"id!: Uuid\", name, git_repo_path, setup_script, dev_script, cleanup_script, copy_files, created_at as \"created_at!: DateTime\", updated_at as \"updated_at!: DateTime\" FROM projects WHERE id = $1", "describe": { "columns": [ { @@ -34,14 +34,19 @@ "type_info": "Text" }, { - "name": "created_at!: DateTime", + "name": "copy_files", "ordinal": 6, "type_info": "Text" }, { - "name": "updated_at!: DateTime", + "name": "created_at!: DateTime", "ordinal": 7, "type_info": "Text" + }, + { + "name": "updated_at!: DateTime", + "ordinal": 8, + "type_info": "Text" } ], "parameters": { @@ -54,9 +59,10 @@ true, true, true, + true, false, false ] }, - "hash": "3a5b3c98a55ca183ab20c74708e3d7e579dda37972c059e7515c4ceee4bd8dd3" + "hash": "821192d8d8a8fba8ce0f144a32e7e500aaa2b6e527b7e7f082a1c73b1f9f9eb8" } diff --git a/crates/db/.sqlx/query-6ecfa16d0cf825aacf233544b5baf151e9adfdca26c226ad71020d291fd802d5.json b/crates/db/.sqlx/query-b95cb59154da69213dea2ded3646d2df2f68293be211cc4f9db0582ea691efee.json similarity index 74% rename from crates/db/.sqlx/query-6ecfa16d0cf825aacf233544b5baf151e9adfdca26c226ad71020d291fd802d5.json rename to crates/db/.sqlx/query-b95cb59154da69213dea2ded3646d2df2f68293be211cc4f9db0582ea691efee.json index 329e4f71..689763bd 100644 --- a/crates/db/.sqlx/query-6ecfa16d0cf825aacf233544b5baf151e9adfdca26c226ad71020d291fd802d5.json +++ b/crates/db/.sqlx/query-b95cb59154da69213dea2ded3646d2df2f68293be211cc4f9db0582ea691efee.json @@ -1,6 +1,6 @@ { "db_name": "SQLite", - "query": "SELECT id as \"id!: Uuid\", name, git_repo_path, setup_script, dev_script, cleanup_script, created_at as \"created_at!: DateTime\", updated_at as \"updated_at!: DateTime\" FROM projects WHERE git_repo_path = $1 AND id != $2", + "query": "SELECT id as \"id!: Uuid\", name, git_repo_path, setup_script, dev_script, cleanup_script, copy_files, created_at as \"created_at!: DateTime\", updated_at as \"updated_at!: DateTime\" FROM projects WHERE git_repo_path = $1 AND id != $2", "describe": { "columns": [ { @@ -34,14 +34,19 @@ "type_info": "Text" }, { - "name": "created_at!: DateTime", + "name": "copy_files", "ordinal": 6, "type_info": "Text" }, { - "name": "updated_at!: DateTime", + "name": "created_at!: DateTime", "ordinal": 7, "type_info": "Text" + }, + { + "name": "updated_at!: DateTime", + "ordinal": 8, + "type_info": "Text" } ], "parameters": { @@ -54,9 +59,10 @@ true, true, true, + true, false, false ] }, - "hash": "6ecfa16d0cf825aacf233544b5baf151e9adfdca26c226ad71020d291fd802d5" + "hash": "b95cb59154da69213dea2ded3646d2df2f68293be211cc4f9db0582ea691efee" } diff --git a/crates/db/migrations/20250811000000_add_copy_files_to_projects.sql b/crates/db/migrations/20250811000000_add_copy_files_to_projects.sql new file mode 100644 index 00000000..09ae996d --- /dev/null +++ b/crates/db/migrations/20250811000000_add_copy_files_to_projects.sql @@ -0,0 +1,3 @@ +-- Add copy_files column to projects table +-- This field stores comma-separated file paths to copy from the original project directory to the worktree +ALTER TABLE projects ADD COLUMN copy_files TEXT; \ No newline at end of file diff --git a/crates/db/src/models/project.rs b/crates/db/src/models/project.rs index 35d4db09..1fc44e40 100644 --- a/crates/db/src/models/project.rs +++ b/crates/db/src/models/project.rs @@ -29,6 +29,7 @@ pub struct Project { pub setup_script: Option, pub dev_script: Option, pub cleanup_script: Option, + pub copy_files: Option, #[ts(type = "Date")] pub created_at: DateTime, @@ -44,6 +45,7 @@ pub struct CreateProject { pub setup_script: Option, pub dev_script: Option, pub cleanup_script: Option, + pub copy_files: Option, } #[derive(Debug, Deserialize, TS)] @@ -53,6 +55,7 @@ pub struct UpdateProject { pub setup_script: Option, pub dev_script: Option, pub cleanup_script: Option, + pub copy_files: Option, } #[derive(Debug, Serialize, TS)] @@ -63,6 +66,7 @@ pub struct ProjectWithBranch { pub setup_script: Option, pub dev_script: Option, pub cleanup_script: Option, + pub copy_files: Option, pub current_branch: Option, #[ts(type = "Date")] @@ -80,6 +84,7 @@ impl ProjectWithBranch { setup_script: project.setup_script, dev_script: project.dev_script, cleanup_script: project.cleanup_script, + copy_files: project.copy_files, current_branch, created_at: project.created_at, updated_at: project.updated_at, @@ -105,7 +110,7 @@ impl Project { pub async fn find_all(pool: &SqlitePool) -> Result, sqlx::Error> { sqlx::query_as!( Project, - r#"SELECT id as "id!: Uuid", name, git_repo_path, setup_script, dev_script, cleanup_script, created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime" FROM projects ORDER BY created_at DESC"# + r#"SELECT id as "id!: Uuid", name, git_repo_path, setup_script, dev_script, cleanup_script, copy_files, created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime" FROM projects ORDER BY created_at DESC"# ) .fetch_all(pool) .await @@ -114,7 +119,7 @@ impl Project { pub async fn find_by_id(pool: &SqlitePool, id: Uuid) -> Result, sqlx::Error> { sqlx::query_as!( Project, - r#"SELECT id as "id!: Uuid", name, git_repo_path, setup_script, dev_script, cleanup_script, created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime" FROM projects WHERE id = $1"#, + r#"SELECT id as "id!: Uuid", name, git_repo_path, setup_script, dev_script, cleanup_script, copy_files, created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime" FROM projects WHERE id = $1"#, id ) .fetch_optional(pool) @@ -127,7 +132,7 @@ impl Project { ) -> Result, sqlx::Error> { sqlx::query_as!( Project, - r#"SELECT id as "id!: Uuid", name, git_repo_path, setup_script, dev_script, cleanup_script, created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime" FROM projects WHERE git_repo_path = $1"#, + r#"SELECT id as "id!: Uuid", name, git_repo_path, setup_script, dev_script, cleanup_script, copy_files, created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime" FROM projects WHERE git_repo_path = $1"#, git_repo_path ) .fetch_optional(pool) @@ -141,7 +146,7 @@ impl Project { ) -> Result, sqlx::Error> { sqlx::query_as!( Project, - r#"SELECT id as "id!: Uuid", name, git_repo_path, setup_script, dev_script, cleanup_script, created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime" FROM projects WHERE git_repo_path = $1 AND id != $2"#, + r#"SELECT id as "id!: Uuid", name, git_repo_path, setup_script, dev_script, cleanup_script, copy_files, created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime" FROM projects WHERE git_repo_path = $1 AND id != $2"#, git_repo_path, exclude_id ) @@ -156,13 +161,14 @@ impl Project { ) -> Result { sqlx::query_as!( Project, - r#"INSERT INTO projects (id, name, git_repo_path, setup_script, dev_script, cleanup_script) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id as "id!: Uuid", name, git_repo_path, setup_script, dev_script, cleanup_script, created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime""#, + r#"INSERT INTO projects (id, name, git_repo_path, setup_script, dev_script, cleanup_script, copy_files) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id as "id!: Uuid", name, git_repo_path, setup_script, dev_script, cleanup_script, copy_files, created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime""#, project_id, data.name, data.git_repo_path, data.setup_script, data.dev_script, - data.cleanup_script + data.cleanup_script, + data.copy_files ) .fetch_one(pool) .await @@ -176,16 +182,18 @@ impl Project { setup_script: Option, dev_script: Option, cleanup_script: Option, + copy_files: Option, ) -> Result { sqlx::query_as!( Project, - r#"UPDATE projects SET name = $2, git_repo_path = $3, setup_script = $4, dev_script = $5, cleanup_script = $6 WHERE id = $1 RETURNING id as "id!: Uuid", name, git_repo_path, setup_script, dev_script, cleanup_script, created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime""#, + r#"UPDATE projects SET name = $2, git_repo_path = $3, setup_script = $4, dev_script = $5, cleanup_script = $6, copy_files = $7 WHERE id = $1 RETURNING id as "id!: Uuid", name, git_repo_path, setup_script, dev_script, cleanup_script, copy_files, created_at as "created_at!: DateTime", updated_at as "updated_at!: DateTime""#, id, name, git_repo_path, setup_script, dev_script, - cleanup_script + cleanup_script, + copy_files ) .fetch_one(pool) .await diff --git a/crates/local-deployment/src/container.rs b/crates/local-deployment/src/container.rs index 33ad280c..d30f4f54 100644 --- a/crates/local-deployment/src/container.rs +++ b/crates/local-deployment/src/container.rs @@ -671,6 +671,17 @@ impl ContainerService for LocalContainerService { ) .await?; + // Copy files specified in the project's copy_files field + if let Some(copy_files) = &project.copy_files + && !copy_files.trim().is_empty() + { + self.copy_project_files(&project.git_repo_path, &worktree_path, copy_files) + .await + .unwrap_or_else(|e| { + tracing::warn!("Failed to copy project files: {}", e); + }); + } + // Update both container_ref and branch in the database TaskAttempt::update_container_ref( &self.db.pool, @@ -933,4 +944,51 @@ impl ContainerService for LocalContainerService { Ok(self.git().commit(Path::new(container_ref), &message)?) } + + /// Copy files from the original project directory to the worktree + async fn copy_project_files( + &self, + source_dir: &PathBuf, + target_dir: &PathBuf, + copy_files: &str, + ) -> Result<(), ContainerError> { + let files: Vec<&str> = copy_files + .split(',') + .map(|s| s.trim()) + .filter(|s| !s.is_empty()) + .collect(); + + for file_path in files { + let source_file = source_dir.join(file_path); + let target_file = target_dir.join(file_path); + + // Create parent directories if needed + if let Some(parent) = target_file.parent() + && !parent.exists() + { + std::fs::create_dir_all(parent).map_err(|e| { + ContainerError::Other(anyhow!("Failed to create directory {:?}: {}", parent, e)) + })?; + } + + // Copy the file + if source_file.exists() { + std::fs::copy(&source_file, &target_file).map_err(|e| { + ContainerError::Other(anyhow!( + "Failed to copy file {:?} to {:?}: {}", + source_file, + target_file, + e + )) + })?; + tracing::info!("Copied file {:?} to worktree", file_path); + } else { + return Err(ContainerError::Other(anyhow!( + "File {:?} does not exist in the project directory", + source_file + ))); + } + } + Ok(()) + } } diff --git a/crates/server/src/routes/projects.rs b/crates/server/src/routes/projects.rs index 8ca9353e..a36565e3 100644 --- a/crates/server/src/routes/projects.rs +++ b/crates/server/src/routes/projects.rs @@ -181,6 +181,7 @@ pub async fn update_project( setup_script, dev_script, cleanup_script, + copy_files, } = payload; let name = name.unwrap_or(existing_project.name); @@ -195,6 +196,7 @@ pub async fn update_project( setup_script, dev_script, cleanup_script, + copy_files, ) .await { diff --git a/crates/services/src/services/container.rs b/crates/services/src/services/container.rs index b8ecd775..d9610a16 100644 --- a/crates/services/src/services/container.rs +++ b/crates/services/src/services/container.rs @@ -123,6 +123,13 @@ pub trait ContainerService { async fn try_commit_changes(&self, ctx: &ExecutionContext) -> Result<(), ContainerError>; + async fn copy_project_files( + &self, + source_dir: &PathBuf, + target_dir: &PathBuf, + copy_files: &str, + ) -> Result<(), ContainerError>; + async fn get_diff( &self, task_attempt: &TaskAttempt, diff --git a/frontend/src/components/projects/copy-files-field.tsx b/frontend/src/components/projects/copy-files-field.tsx new file mode 100644 index 00000000..8451263a --- /dev/null +++ b/frontend/src/components/projects/copy-files-field.tsx @@ -0,0 +1,43 @@ +import { MultiFileSearchTextarea } from '@/components/ui/multi-file-search-textarea'; + +interface CopyFilesFieldProps { + value: string; + onChange: (value: string) => void; + projectId?: string; + disabled?: boolean; +} + +export function CopyFilesField({ + value, + onChange, + projectId, + disabled = false, +}: CopyFilesFieldProps) { + if (projectId) { + // Editing existing project - use file search + return ( + + ); + } + + // Creating new project - fall back to plain textarea + return ( +