diff --git a/crates/db/.sqlx/query-ff1ddf3ed4bed81adf8e1d3b8e3c3c8f0fcb9dc51511250f3c32501d8d60f0f0.json b/crates/db/.sqlx/query-0aa72a50b347c42962b3e48c11c9ddd1874e3e807cc1563d2aaa77a473ac3aae.json
similarity index 69%
rename from crates/db/.sqlx/query-ff1ddf3ed4bed81adf8e1d3b8e3c3c8f0fcb9dc51511250f3c32501d8d60f0f0.json
rename to crates/db/.sqlx/query-0aa72a50b347c42962b3e48c11c9ddd1874e3e807cc1563d2aaa77a473ac3aae.json
index b076836a5e..0b972f93a5 100644
--- a/crates/db/.sqlx/query-ff1ddf3ed4bed81adf8e1d3b8e3c3c8f0fcb9dc51511250f3c32501d8d60f0f0.json
+++ b/crates/db/.sqlx/query-0aa72a50b347c42962b3e48c11c9ddd1874e3e807cc1563d2aaa77a473ac3aae.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "INSERT INTO projects (\n id,\n name\n ) VALUES (\n $1, $2\n )\n RETURNING id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"",
+ "query": "INSERT INTO projects (\n id,\n name\n ) VALUES (\n $1, $2\n )\n RETURNING id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n dev_server_timeout,\n dev_server_port,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"",
"describe": {
"columns": [
{
@@ -29,18 +29,28 @@
"type_info": "Text"
},
{
- "name": "remote_project_id: Uuid",
+ "name": "dev_server_timeout",
"ordinal": 5,
+ "type_info": "Integer"
+ },
+ {
+ "name": "dev_server_port",
+ "ordinal": 6,
+ "type_info": "Integer"
+ },
+ {
+ "name": "remote_project_id: Uuid",
+ "ordinal": 7,
"type_info": "Blob"
},
{
"name": "created_at!: DateTime",
- "ordinal": 6,
+ "ordinal": 8,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime",
- "ordinal": 7,
+ "ordinal": 9,
"type_info": "Text"
}
],
@@ -54,9 +64,11 @@
false,
false,
true,
+ true,
+ true,
false,
false
]
},
- "hash": "ff1ddf3ed4bed81adf8e1d3b8e3c3c8f0fcb9dc51511250f3c32501d8d60f0f0"
+ "hash": "0aa72a50b347c42962b3e48c11c9ddd1874e3e807cc1563d2aaa77a473ac3aae"
}
diff --git a/crates/db/.sqlx/query-88c15c5c9f2c8edeef4d48c5fcad3607fbc3f7d1402d379d19ad3f4ae2dd0f7d.json b/crates/db/.sqlx/query-4d24da063ad43ca873e7140cbc4507ad74e36a0239902410c468e2539ae2d629.json
similarity index 63%
rename from crates/db/.sqlx/query-88c15c5c9f2c8edeef4d48c5fcad3607fbc3f7d1402d379d19ad3f4ae2dd0f7d.json
rename to crates/db/.sqlx/query-4d24da063ad43ca873e7140cbc4507ad74e36a0239902410c468e2539ae2d629.json
index 4099b7f012..6a331e3c60 100644
--- a/crates/db/.sqlx/query-88c15c5c9f2c8edeef4d48c5fcad3607fbc3f7d1402d379d19ad3f4ae2dd0f7d.json
+++ b/crates/db/.sqlx/query-4d24da063ad43ca873e7140cbc4507ad74e36a0239902410c468e2539ae2d629.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n WHERE remote_project_id = $1\n LIMIT 1",
+ "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n dev_server_timeout,\n dev_server_port,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n WHERE remote_project_id = $1\n LIMIT 1",
"describe": {
"columns": [
{
@@ -29,18 +29,28 @@
"type_info": "Text"
},
{
- "name": "remote_project_id: Uuid",
+ "name": "dev_server_timeout",
"ordinal": 5,
+ "type_info": "Integer"
+ },
+ {
+ "name": "dev_server_port",
+ "ordinal": 6,
+ "type_info": "Integer"
+ },
+ {
+ "name": "remote_project_id: Uuid",
+ "ordinal": 7,
"type_info": "Blob"
},
{
"name": "created_at!: DateTime",
- "ordinal": 6,
+ "ordinal": 8,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime",
- "ordinal": 7,
+ "ordinal": 9,
"type_info": "Text"
}
],
@@ -54,9 +64,11 @@
true,
true,
true,
+ true,
+ true,
false,
false
]
},
- "hash": "88c15c5c9f2c8edeef4d48c5fcad3607fbc3f7d1402d379d19ad3f4ae2dd0f7d"
+ "hash": "4d24da063ad43ca873e7140cbc4507ad74e36a0239902410c468e2539ae2d629"
}
diff --git a/crates/db/.sqlx/query-368d9be9608fea5002627625bf8abd3e9073e06bb72fc75f11c2af13f1d43a10.json b/crates/db/.sqlx/query-97dad9913b60650871180864e456423bc92d6d305cf909451e21bc9c45fb5f97.json
similarity index 58%
rename from crates/db/.sqlx/query-368d9be9608fea5002627625bf8abd3e9073e06bb72fc75f11c2af13f1d43a10.json
rename to crates/db/.sqlx/query-97dad9913b60650871180864e456423bc92d6d305cf909451e21bc9c45fb5f97.json
index 910193cc88..7977928857 100644
--- a/crates/db/.sqlx/query-368d9be9608fea5002627625bf8abd3e9073e06bb72fc75f11c2af13f1d43a10.json
+++ b/crates/db/.sqlx/query-97dad9913b60650871180864e456423bc92d6d305cf909451e21bc9c45fb5f97.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "\n SELECT p.id as \"id!: Uuid\", p.name, p.dev_script, p.dev_script_working_dir,\n p.default_agent_working_dir,\n p.remote_project_id as \"remote_project_id: Uuid\",\n p.created_at as \"created_at!: DateTime\", p.updated_at as \"updated_at!: DateTime\"\n FROM projects p\n WHERE p.id IN (\n SELECT DISTINCT t.project_id\n FROM tasks t\n INNER JOIN workspaces w ON w.task_id = t.id\n ORDER BY w.updated_at DESC\n )\n LIMIT $1\n ",
+ "query": "\n SELECT p.id as \"id!: Uuid\", p.name, p.dev_script, p.dev_script_working_dir,\n p.default_agent_working_dir, p.dev_server_timeout, p.dev_server_port,\n p.remote_project_id as \"remote_project_id: Uuid\",\n p.created_at as \"created_at!: DateTime\", p.updated_at as \"updated_at!: DateTime\"\n FROM projects p\n WHERE p.id IN (\n SELECT DISTINCT t.project_id\n FROM tasks t\n INNER JOIN workspaces w ON w.task_id = t.id\n ORDER BY w.updated_at DESC\n )\n LIMIT $1\n ",
"describe": {
"columns": [
{
@@ -29,18 +29,28 @@
"type_info": "Text"
},
{
- "name": "remote_project_id: Uuid",
+ "name": "dev_server_timeout",
"ordinal": 5,
+ "type_info": "Integer"
+ },
+ {
+ "name": "dev_server_port",
+ "ordinal": 6,
+ "type_info": "Integer"
+ },
+ {
+ "name": "remote_project_id: Uuid",
+ "ordinal": 7,
"type_info": "Blob"
},
{
"name": "created_at!: DateTime",
- "ordinal": 6,
+ "ordinal": 8,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime",
- "ordinal": 7,
+ "ordinal": 9,
"type_info": "Text"
}
],
@@ -54,9 +64,11 @@
true,
true,
true,
+ true,
+ true,
false,
false
]
},
- "hash": "368d9be9608fea5002627625bf8abd3e9073e06bb72fc75f11c2af13f1d43a10"
+ "hash": "97dad9913b60650871180864e456423bc92d6d305cf909451e21bc9c45fb5f97"
}
diff --git a/crates/db/.sqlx/query-1eae64d51ea1d81c7239fc3640bb15bb59a51333602e7050bec3f5e8fc7fc782.json b/crates/db/.sqlx/query-a26746f5dcc1262d76d9c883ec312980964c0f06ab18f2925e92c7135cb6863c.json
similarity index 65%
rename from crates/db/.sqlx/query-1eae64d51ea1d81c7239fc3640bb15bb59a51333602e7050bec3f5e8fc7fc782.json
rename to crates/db/.sqlx/query-a26746f5dcc1262d76d9c883ec312980964c0f06ab18f2925e92c7135cb6863c.json
index 263900474a..8b50627fa4 100644
--- a/crates/db/.sqlx/query-1eae64d51ea1d81c7239fc3640bb15bb59a51333602e7050bec3f5e8fc7fc782.json
+++ b/crates/db/.sqlx/query-a26746f5dcc1262d76d9c883ec312980964c0f06ab18f2925e92c7135cb6863c.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n WHERE id = $1",
+ "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n dev_server_timeout,\n dev_server_port,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n WHERE rowid = $1",
"describe": {
"columns": [
{
@@ -29,18 +29,28 @@
"type_info": "Text"
},
{
- "name": "remote_project_id: Uuid",
+ "name": "dev_server_timeout",
"ordinal": 5,
+ "type_info": "Integer"
+ },
+ {
+ "name": "dev_server_port",
+ "ordinal": 6,
+ "type_info": "Integer"
+ },
+ {
+ "name": "remote_project_id: Uuid",
+ "ordinal": 7,
"type_info": "Blob"
},
{
"name": "created_at!: DateTime",
- "ordinal": 6,
+ "ordinal": 8,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime",
- "ordinal": 7,
+ "ordinal": 9,
"type_info": "Text"
}
],
@@ -54,9 +64,11 @@
true,
true,
true,
+ true,
+ true,
false,
false
]
},
- "hash": "1eae64d51ea1d81c7239fc3640bb15bb59a51333602e7050bec3f5e8fc7fc782"
+ "hash": "a26746f5dcc1262d76d9c883ec312980964c0f06ab18f2925e92c7135cb6863c"
}
diff --git a/crates/db/.sqlx/query-17789c934ca49a04a85505f1a9c861a575ee8fa2e8b6c9e9f4fc177c09caa53d.json b/crates/db/.sqlx/query-c3390d68d841bd5802cce574df7cc459610690f99bbe5dc85ac7a4d08a6122f8.json
similarity index 65%
rename from crates/db/.sqlx/query-17789c934ca49a04a85505f1a9c861a575ee8fa2e8b6c9e9f4fc177c09caa53d.json
rename to crates/db/.sqlx/query-c3390d68d841bd5802cce574df7cc459610690f99bbe5dc85ac7a4d08a6122f8.json
index 4b5eed086e..7fb0f50bec 100644
--- a/crates/db/.sqlx/query-17789c934ca49a04a85505f1a9c861a575ee8fa2e8b6c9e9f4fc177c09caa53d.json
+++ b/crates/db/.sqlx/query-c3390d68d841bd5802cce574df7cc459610690f99bbe5dc85ac7a4d08a6122f8.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n WHERE rowid = $1",
+ "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n dev_server_timeout,\n dev_server_port,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n WHERE id = $1",
"describe": {
"columns": [
{
@@ -29,18 +29,28 @@
"type_info": "Text"
},
{
- "name": "remote_project_id: Uuid",
+ "name": "dev_server_timeout",
"ordinal": 5,
+ "type_info": "Integer"
+ },
+ {
+ "name": "dev_server_port",
+ "ordinal": 6,
+ "type_info": "Integer"
+ },
+ {
+ "name": "remote_project_id: Uuid",
+ "ordinal": 7,
"type_info": "Blob"
},
{
"name": "created_at!: DateTime",
- "ordinal": 6,
+ "ordinal": 8,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime",
- "ordinal": 7,
+ "ordinal": 9,
"type_info": "Text"
}
],
@@ -54,9 +64,11 @@
true,
true,
true,
+ true,
+ true,
false,
false
]
},
- "hash": "17789c934ca49a04a85505f1a9c861a575ee8fa2e8b6c9e9f4fc177c09caa53d"
+ "hash": "c3390d68d841bd5802cce574df7cc459610690f99bbe5dc85ac7a4d08a6122f8"
}
diff --git a/crates/db/.sqlx/query-9cdc6d55c24ff020a6599f59560c7f0d3feacfe64136bb1338bb4c447022f69b.json b/crates/db/.sqlx/query-d42253b8170e254767c2fe6d6817ddd5ab4fc957d799c09cd1562a438165c3d4.json
similarity index 64%
rename from crates/db/.sqlx/query-9cdc6d55c24ff020a6599f59560c7f0d3feacfe64136bb1338bb4c447022f69b.json
rename to crates/db/.sqlx/query-d42253b8170e254767c2fe6d6817ddd5ab4fc957d799c09cd1562a438165c3d4.json
index 08cd108b94..25f989d5ff 100644
--- a/crates/db/.sqlx/query-9cdc6d55c24ff020a6599f59560c7f0d3feacfe64136bb1338bb4c447022f69b.json
+++ b/crates/db/.sqlx/query-d42253b8170e254767c2fe6d6817ddd5ab4fc957d799c09cd1562a438165c3d4.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n ORDER BY created_at DESC",
+ "query": "SELECT id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n dev_server_timeout,\n dev_server_port,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"\n FROM projects\n ORDER BY created_at DESC",
"describe": {
"columns": [
{
@@ -29,18 +29,28 @@
"type_info": "Text"
},
{
- "name": "remote_project_id: Uuid",
+ "name": "dev_server_timeout",
"ordinal": 5,
+ "type_info": "Integer"
+ },
+ {
+ "name": "dev_server_port",
+ "ordinal": 6,
+ "type_info": "Integer"
+ },
+ {
+ "name": "remote_project_id: Uuid",
+ "ordinal": 7,
"type_info": "Blob"
},
{
"name": "created_at!: DateTime",
- "ordinal": 6,
+ "ordinal": 8,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime",
- "ordinal": 7,
+ "ordinal": 9,
"type_info": "Text"
}
],
@@ -54,9 +64,11 @@
true,
true,
true,
+ true,
+ true,
false,
false
]
},
- "hash": "9cdc6d55c24ff020a6599f59560c7f0d3feacfe64136bb1338bb4c447022f69b"
+ "hash": "d42253b8170e254767c2fe6d6817ddd5ab4fc957d799c09cd1562a438165c3d4"
}
diff --git a/crates/db/.sqlx/query-697001fc14562702ea84061e74bdcc6b9fbef679b8366ab46c1f629a633e9919.json b/crates/db/.sqlx/query-ecffdea9549e5bcdbf0bff135c670671a79ee300e3242f20d1a5ffe96b8b5a97.json
similarity index 55%
rename from crates/db/.sqlx/query-697001fc14562702ea84061e74bdcc6b9fbef679b8366ab46c1f629a633e9919.json
rename to crates/db/.sqlx/query-ecffdea9549e5bcdbf0bff135c670671a79ee300e3242f20d1a5ffe96b8b5a97.json
index b814774124..3292258ba3 100644
--- a/crates/db/.sqlx/query-697001fc14562702ea84061e74bdcc6b9fbef679b8366ab46c1f629a633e9919.json
+++ b/crates/db/.sqlx/query-ecffdea9549e5bcdbf0bff135c670671a79ee300e3242f20d1a5ffe96b8b5a97.json
@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
- "query": "UPDATE projects\n SET name = $2, dev_script = $3, dev_script_working_dir = $4, default_agent_working_dir = $5\n WHERE id = $1\n RETURNING id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"",
+ "query": "UPDATE projects\n SET name = $2, dev_script = $3, dev_script_working_dir = $4, default_agent_working_dir = $5, dev_server_timeout = $6, dev_server_port = $7\n WHERE id = $1\n RETURNING id as \"id!: Uuid\",\n name,\n dev_script,\n dev_script_working_dir,\n default_agent_working_dir,\n dev_server_timeout,\n dev_server_port,\n remote_project_id as \"remote_project_id: Uuid\",\n created_at as \"created_at!: DateTime\",\n updated_at as \"updated_at!: DateTime\"",
"describe": {
"columns": [
{
@@ -29,23 +29,33 @@
"type_info": "Text"
},
{
- "name": "remote_project_id: Uuid",
+ "name": "dev_server_timeout",
"ordinal": 5,
+ "type_info": "Integer"
+ },
+ {
+ "name": "dev_server_port",
+ "ordinal": 6,
+ "type_info": "Integer"
+ },
+ {
+ "name": "remote_project_id: Uuid",
+ "ordinal": 7,
"type_info": "Blob"
},
{
"name": "created_at!: DateTime",
- "ordinal": 6,
+ "ordinal": 8,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime",
- "ordinal": 7,
+ "ordinal": 9,
"type_info": "Text"
}
],
"parameters": {
- "Right": 5
+ "Right": 7
},
"nullable": [
true,
@@ -54,9 +64,11 @@
true,
true,
true,
+ true,
+ true,
false,
false
]
},
- "hash": "697001fc14562702ea84061e74bdcc6b9fbef679b8366ab46c1f629a633e9919"
+ "hash": "ecffdea9549e5bcdbf0bff135c670671a79ee300e3242f20d1a5ffe96b8b5a97"
}
diff --git a/crates/db/migrations/20260104000000_add_dev_server_timeout_to_projects.sql b/crates/db/migrations/20260104000000_add_dev_server_timeout_to_projects.sql
new file mode 100644
index 0000000000..01049ead77
--- /dev/null
+++ b/crates/db/migrations/20260104000000_add_dev_server_timeout_to_projects.sql
@@ -0,0 +1,2 @@
+ALTER TABLE projects ADD COLUMN dev_server_timeout INTEGER DEFAULT NULL;
+ALTER TABLE projects ADD COLUMN dev_server_port INTEGER DEFAULT NULL;
diff --git a/crates/db/src/models/project.rs b/crates/db/src/models/project.rs
index c8a61e4698..f0752467bc 100644
--- a/crates/db/src/models/project.rs
+++ b/crates/db/src/models/project.rs
@@ -24,6 +24,11 @@ pub struct Project {
pub dev_script: Option,
pub dev_script_working_dir: Option,
pub default_agent_working_dir: Option,
+ /// Timeout in seconds before showing the "trouble previewing" help message.
+ /// Defaults to 30 seconds if not set.
+ pub dev_server_timeout: Option,
+ /// Optional port for the dev server. If set, this port will be used instead of auto-detection.
+ pub dev_server_port: Option,
pub remote_project_id: Option,
#[ts(type = "Date")]
pub created_at: DateTime,
@@ -43,6 +48,10 @@ pub struct UpdateProject {
pub dev_script: Option,
pub dev_script_working_dir: Option,
pub default_agent_working_dir: Option,
+ /// Timeout in seconds before showing the "trouble previewing" help message.
+ pub dev_server_timeout: Option,
+ /// Optional port for the dev server.
+ pub dev_server_port: Option,
}
#[derive(Debug, Serialize, TS)]
@@ -77,6 +86,8 @@ impl Project {
dev_script,
dev_script_working_dir,
default_agent_working_dir,
+ dev_server_timeout,
+ dev_server_port,
remote_project_id as "remote_project_id: Uuid",
created_at as "created_at!: DateTime",
updated_at as "updated_at!: DateTime"
@@ -93,7 +104,7 @@ impl Project {
Project,
r#"
SELECT p.id as "id!: Uuid", p.name, p.dev_script, p.dev_script_working_dir,
- p.default_agent_working_dir,
+ p.default_agent_working_dir, p.dev_server_timeout, p.dev_server_port,
p.remote_project_id as "remote_project_id: Uuid",
p.created_at as "created_at!: DateTime", p.updated_at as "updated_at!: DateTime"
FROM projects p
@@ -119,6 +130,8 @@ impl Project {
dev_script,
dev_script_working_dir,
default_agent_working_dir,
+ dev_server_timeout,
+ dev_server_port,
remote_project_id as "remote_project_id: Uuid",
created_at as "created_at!: DateTime",
updated_at as "updated_at!: DateTime"
@@ -138,6 +151,8 @@ impl Project {
dev_script,
dev_script_working_dir,
default_agent_working_dir,
+ dev_server_timeout,
+ dev_server_port,
remote_project_id as "remote_project_id: Uuid",
created_at as "created_at!: DateTime",
updated_at as "updated_at!: DateTime"
@@ -160,6 +175,8 @@ impl Project {
dev_script,
dev_script_working_dir,
default_agent_working_dir,
+ dev_server_timeout,
+ dev_server_port,
remote_project_id as "remote_project_id: Uuid",
created_at as "created_at!: DateTime",
updated_at as "updated_at!: DateTime"
@@ -190,6 +207,8 @@ impl Project {
dev_script,
dev_script_working_dir,
default_agent_working_dir,
+ dev_server_timeout,
+ dev_server_port,
remote_project_id as "remote_project_id: Uuid",
created_at as "created_at!: DateTime",
updated_at as "updated_at!: DateTime""#,
@@ -213,17 +232,21 @@ impl Project {
let dev_script = payload.dev_script.clone();
let dev_script_working_dir = payload.dev_script_working_dir.clone();
let default_agent_working_dir = payload.default_agent_working_dir.clone();
+ let dev_server_timeout = payload.dev_server_timeout;
+ let dev_server_port = payload.dev_server_port;
sqlx::query_as!(
Project,
r#"UPDATE projects
- SET name = $2, dev_script = $3, dev_script_working_dir = $4, default_agent_working_dir = $5
+ SET name = $2, dev_script = $3, dev_script_working_dir = $4, default_agent_working_dir = $5, dev_server_timeout = $6, dev_server_port = $7
WHERE id = $1
RETURNING id as "id!: Uuid",
name,
dev_script,
dev_script_working_dir,
default_agent_working_dir,
+ dev_server_timeout,
+ dev_server_port,
remote_project_id as "remote_project_id: Uuid",
created_at as "created_at!: DateTime",
updated_at as "updated_at!: DateTime""#,
@@ -232,6 +255,8 @@ impl Project {
dev_script,
dev_script_working_dir,
default_agent_working_dir,
+ dev_server_timeout,
+ dev_server_port,
)
.fetch_one(pool)
.await
diff --git a/crates/services/src/services/container.rs b/crates/services/src/services/container.rs
index 5e3c4c6ebb..8d9ff35812 100644
--- a/crates/services/src/services/container.rs
+++ b/crates/services/src/services/container.rs
@@ -413,6 +413,8 @@ pub trait ContainerService {
} else {
project.default_agent_working_dir.clone()
},
+ dev_server_timeout: project.dev_server_timeout,
+ dev_server_port: project.dev_server_port,
},
)
.await?;
diff --git a/crates/services/src/services/project.rs b/crates/services/src/services/project.rs
index 854c08f4d4..63de383b63 100644
--- a/crates/services/src/services/project.rs
+++ b/crates/services/src/services/project.rs
@@ -132,6 +132,8 @@ impl ProjectService {
dev_script: None,
dev_script_working_dir: None,
default_agent_working_dir: Some(repo.name),
+ dev_server_timeout: None,
+ dev_server_port: None,
},
)
.await?;
diff --git a/frontend/src/components/panels/PreviewPanel.tsx b/frontend/src/components/panels/PreviewPanel.tsx
index 07c45992f6..e9e9553ad5 100644
--- a/frontend/src/components/panels/PreviewPanel.tsx
+++ b/frontend/src/components/panels/PreviewPanel.tsx
@@ -44,7 +44,16 @@ export function PreviewPanel() {
} = useDevServer(attemptId);
const logStream = useLogStream(latestDevServerProcess?.id ?? '');
- const lastKnownUrl = useDevserverUrlFromLogs(logStream.logs);
+ const autoDetectedUrl = useDevserverUrlFromLogs(logStream.logs);
+
+ // Use configured port if set (takes priority), otherwise use auto-detected URL
+ const lastKnownUrl = project?.dev_server_port
+ ? {
+ url: `http://${window.location.hostname}:${project.dev_server_port}`,
+ port: Number(project.dev_server_port),
+ scheme: 'http' as const,
+ }
+ : autoDetectedUrl;
const previewState = useDevserverPreview(attemptId, {
projectHasDevScript,
@@ -96,21 +105,33 @@ export function PreviewPanel() {
};
}, [previewState.status, previewState.url, addElement]);
+ // Timeout before showing the "trouble previewing" help message
+ // Uses project setting if configured, otherwise defaults to 30 seconds
+ const DEFAULT_DEV_SERVER_TIMEOUT_SECONDS = 30;
+ const devServerTimeoutMs =
+ Number(project?.dev_server_timeout ?? DEFAULT_DEV_SERVER_TIMEOUT_SECONDS) * 1000;
+
function startTimer() {
setLoadingTimeFinished(false);
setTimeout(() => {
setLoadingTimeFinished(true);
- }, 5000);
+ }, devServerTimeoutMs);
}
useEffect(() => {
startTimer();
}, []);
+ const isPreviewReady =
+ previewState.status === 'ready' &&
+ Boolean(previewState.url) &&
+ !iframeError;
+
useEffect(() => {
if (
loadingTimeFinished &&
!isReady &&
+ !isPreviewReady &&
latestDevServerProcess &&
runningDevServer
) {
@@ -118,11 +139,14 @@ export function PreviewPanel() {
setShowLogs(true);
setLoadingTimeFinished(false);
}
- }, [loadingTimeFinished, isReady, latestDevServerProcess, runningDevServer]);
+ }, [
+ loadingTimeFinished,
+ isReady,
+ isPreviewReady,
+ latestDevServerProcess,
+ runningDevServer,
+ ]);
- const isPreviewReady =
- (previewState.status === 'ready' && Boolean(previewState.url)) ||
- (customUrl !== null && runningDevServer);
const isPreviewReadyWithoutError = isPreviewReady && !iframeError;
const mode = iframeError
? 'error'
diff --git a/frontend/src/components/tasks/TaskDetails/preview/NoServerContent.tsx b/frontend/src/components/tasks/TaskDetails/preview/NoServerContent.tsx
index 5faa7ebc20..c930989194 100644
--- a/frontend/src/components/tasks/TaskDetails/preview/NoServerContent.tsx
+++ b/frontend/src/components/tasks/TaskDetails/preview/NoServerContent.tsx
@@ -87,6 +87,8 @@ export function NoServerContent({
dev_script: script,
dev_script_working_dir: project.dev_script_working_dir ?? null,
default_agent_working_dir: project.default_agent_working_dir ?? null,
+ dev_server_timeout: project.dev_server_timeout ?? null,
+ dev_server_port: project.dev_server_port ?? null,
},
},
{
diff --git a/frontend/src/pages/settings/ProjectSettings.tsx b/frontend/src/pages/settings/ProjectSettings.tsx
index 6e28c897b7..5c5d10f997 100644
--- a/frontend/src/pages/settings/ProjectSettings.tsx
+++ b/frontend/src/pages/settings/ProjectSettings.tsx
@@ -38,6 +38,8 @@ interface ProjectFormState {
dev_script: string;
dev_script_working_dir: string;
default_agent_working_dir: string;
+ dev_server_timeout: string;
+ dev_server_port: string;
}
interface RepoScriptsFormState {
@@ -53,6 +55,8 @@ function projectToFormState(project: Project): ProjectFormState {
dev_script: project.dev_script ?? '',
dev_script_working_dir: project.dev_script_working_dir ?? '',
default_agent_working_dir: project.default_agent_working_dir ?? '',
+ dev_server_timeout: project.dev_server_timeout?.toString() ?? '',
+ dev_server_port: project.dev_server_port?.toString() ?? '',
};
}
@@ -391,12 +395,16 @@ export function ProjectSettings() {
setSuccess(false);
try {
+ const timeoutValue = draft.dev_server_timeout.trim();
+ const portValue = draft.dev_server_port.trim();
const updateData: UpdateProject = {
name: draft.name.trim(),
dev_script: draft.dev_script.trim() || null,
dev_script_working_dir: draft.dev_script_working_dir.trim() || null,
default_agent_working_dir:
draft.default_agent_working_dir.trim() || null,
+ dev_server_timeout: timeoutValue ? Number(timeoutValue) : null,
+ dev_server_port: portValue ? Number(portValue) : null,
};
updateProject.mutate({
@@ -610,6 +618,47 @@ export function ProjectSettings() {
+
+
+
+ updateDraft({ dev_server_port: e.target.value })
+ }
+ placeholder="Auto-detect"
+ className="font-mono w-32"
+ />
+
+ Port for the dev server preview. Leave empty to auto-detect
+ from dev server logs.
+
+
+
+
+
+
+ updateDraft({ dev_server_timeout: e.target.value })
+ }
+ placeholder="30"
+ className="font-mono w-32"
+ />
+
+ Time to wait before showing the "trouble previewing"
+ help message. Default is 30 seconds.
+
+
+