Add app update detection and multi-folder project support
All checks were successful
Build App / build-linux (push) Successful in 2m54s
Build App / build-windows (push) Successful in 4m18s
Build Container / build-container (push) Successful in 1m30s

Feature 1 - Update Detection: Query Gitea releases API on startup (3s
delay) and every 24h, compare patch versions by platform, show pulsing
"Update" button in TopBar with dialog for release notes/downloads.
Settings: auto-check toggle, manual check, dismiss per-version.

Feature 2 - Multi-Folder Projects: Replace single `path` with
`paths: Vec<ProjectPath>` (host_path + mount_name). Each folder mounts
to `/workspace/{mount_name}`. Auto-migrate old single-path JSON on load.
Container recreation via paths-fingerprint label. AddProjectDialog and
ProjectCard support add/remove/edit of multiple folders.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 21:18:33 +00:00
parent 854f59a95a
commit 7e1cc92aa4
23 changed files with 1163 additions and 98 deletions

View File

@@ -6,11 +6,17 @@ pub struct EnvVar {
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProjectPath {
pub host_path: String,
pub mount_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Project {
pub id: String,
pub name: String,
pub path: String,
pub paths: Vec<ProjectPath>,
pub container_id: Option<String>,
pub status: ProjectStatus,
pub auth_mode: AuthMode,
@@ -91,12 +97,12 @@ pub struct BedrockConfig {
}
impl Project {
pub fn new(name: String, path: String) -> Self {
pub fn new(name: String, paths: Vec<ProjectPath>) -> Self {
let now = chrono::Utc::now().to_rfc3339();
Self {
id: uuid::Uuid::new_v4().to_string(),
name,
path,
paths,
container_id: None,
status: ProjectStatus::Stopped,
auth_mode: AuthMode::default(),
@@ -116,4 +122,29 @@ impl Project {
pub fn container_name(&self) -> String {
format!("triple-c-{}", self.id)
}
/// Migrate a project JSON value from old single-`path` format to new `paths` format.
/// If the value already has `paths`, it is returned unchanged.
pub fn migrate_from_value(mut val: serde_json::Value) -> serde_json::Value {
if let Some(obj) = val.as_object_mut() {
if obj.contains_key("paths") {
return val;
}
if let Some(path_val) = obj.remove("path") {
let path_str = path_val.as_str().unwrap_or("").to_string();
let mount_name = path_str
.trim_end_matches(['/', '\\'])
.rsplit(['/', '\\'])
.next()
.unwrap_or("workspace")
.to_string();
let project_path = serde_json::json!([{
"host_path": path_str,
"mount_name": if mount_name.is_empty() { "workspace".to_string() } else { mount_name },
}]);
obj.insert("paths".to_string(), project_path);
}
}
val
}
}