Projekt-Identität und -Config neu: .ai-control/config.json mit UUID und Name

- Projekt-Config zieht von ai-control.json nach .ai-control/config.json um,
  das Icon liegt daneben und synct mit dem Projekt.
- Jedes Projekt trägt eine UUID (id) und den Anzeigenamen (name) in der
  Config; die UUID ist Registry-Schlüssel und project-Parameter aller
  Commands, der Name reine Darstellung.
- Pool-Zuordnung ist maschinenlokal und liegt in der projects.json-Registry,
  nicht mehr in der syncbaren Projekt-Config.
- open_terminal prüft die ID im Ordner gegen die Registry (verschobene oder
  ersetzte Ordner scheitern laut).
- Import übernimmt eine mitgebrachte ID, legt Arbeitsordner und Archiv-Home
  an und zieht die Archiv-Permission nach.
- Migration beim App-Start: Alt-Datei umziehen, Pool in die Registry,
  Icons aus ~/.config/ai-control/icons ins Projekt, Registry auf UUIDs.
- ProjectConfig reicht unbekannte Keys per serde(flatten) durch den
  Round-Trip (Regressionstest config_roundtrip_erhaelt_fremde_keys).
This commit is contained in:
marcus hinz
2026-07-20 10:55:21 +02:00
parent 6d44f77671
commit b9d006184a
20 changed files with 830 additions and 346 deletions
+9 -8
View File
@@ -1,11 +1,12 @@
//! Pro-Projekt-.desktop-Dateien: dash-to-dock/GNOME ordnen dem Fenster
//! (app_id `aicontrol-<projekt>`, via set_prgname) darüber das Projekt-Icon zu.
//! (app_id `aicontrol-<projekt-id>`, via set_prgname) darüber Icon und
//! Anzeigename des Projekts zu.
use std::fs;
use std::path::PathBuf;
use crate::domain::paths::Paths;
use crate::domain::project::{read_project_config_in, resolve_icon_path, TerminalConfig};
use crate::domain::project::{read_project_config_in, resolve_icon_path, ProjectConfig};
use crate::domain::registry::load_registry;
/// ~/.local/share/applications — Ziel der pro-Terminal-.desktop-Dateien.
@@ -16,7 +17,7 @@ fn applications_dir(paths: &Paths) -> PathBuf {
/// Schreibt/aktualisiert die .desktop eines Projekts. NoDisplay=true hält den
/// App-Starter sauber.
pub(crate) fn write_terminal_desktop(paths: &Paths, project: &str, cfg: &TerminalConfig) {
pub(crate) fn write_terminal_desktop(paths: &Paths, project: &str, cfg: &ProjectConfig) {
let dir = applications_dir(paths);
if fs::create_dir_all(&dir).is_err() {
return;
@@ -26,15 +27,17 @@ pub(crate) fn write_terminal_desktop(paths: &Paths, project: &str, cfg: &Termina
.and_then(|p| p.to_str().map(str::to_string))
.unwrap_or_else(|| "ai-control".into());
let icon_line = cfg
.terminal
.icon
.as_deref()
.map(|i| resolve_icon_path(paths, i))
.and_then(|i| resolve_icon_path(paths, project, i).ok())
.filter(|p| p.exists())
.and_then(|p| p.to_str().map(str::to_string))
.map(|p| format!("Icon={p}\n"))
.unwrap_or_default();
let name = cfg.name.as_deref().unwrap_or(project);
let content = format!(
"[Desktop Entry]\nType=Application\nName={project}\nExec={exec} --terminal {project}\n\
"[Desktop Entry]\nType=Application\nName={name}\nExec={exec} --terminal {project}\n\
{icon_line}StartupWMClass=aicontrol-{project}\nNoDisplay=true\n"
);
let _ = fs::write(dir.join(format!("aicontrol-{project}.desktop")), content);
@@ -53,9 +56,7 @@ pub(crate) fn sync_all_desktops(paths: &Paths) {
return;
};
for project in reg.keys() {
let cfg = read_project_config_in(paths, project)
.map(|c| c.terminal)
.unwrap_or_default();
let cfg = read_project_config_in(paths, project).unwrap_or_default();
write_terminal_desktop(paths, project, &cfg);
}
let dir = applications_dir(paths);