Projekt löschen in drei Stufen mit Artefakt-Vorschau; Listen-Scroll gefixt

Eskalationsleiter: 'integration' nimmt nur die ai-control-Spuren zurück
(Registry, .ai-control/, Archiv-Rechte + Todo-Hook in settings.json,
Panel-Dateien, .desktop — Ordner, memory/ und Claude-Code-Grundgerüst
bleiben), 'archive' löscht zusätzlich das Archiv, 'full' den Projekt-
ordner mit Arbeitsordner-Flag. delete_preview liefert die konkrete
Artefaktliste; der Dialog zeigt sie je Stufe. Ersetzt delete_project/
remove_project (Handler, App-ACL-Manifest und Capability nachgezogen).

Außerdem: main scrollt nicht mehr — Toolbars und Fehlerzeile stehen,
nur die Listen scrollen (alle drei Tabs, .list-scroll).
This commit is contained in:
marcus hinz
2026-07-20 13:37:02 +02:00
parent 6682040cc0
commit 6c765ff7b5
73 changed files with 1250 additions and 102 deletions
+67 -1
View File
@@ -1,3 +1,69 @@
fn main() {
tauri_build::build()
// App-ACL-Manifest: erzeugt pro Command allow-/deny-Permissions. Sobald das
// Manifest existiert, gilt deny-by-default — jedes Command muss in einer
// Capability einem Fenster zugeteilt sein (capabilities/*.json), sonst
// lehnt Tauri den Aufruf ab. Neue Commands hier UND in der passenden
// Capability eintragen.
tauri_build::try_build(
tauri_build::Attributes::new().app_manifest(tauri_build::AppManifest::new().commands(&[
"list_projects",
"create_project_full",
"add_project",
"delete_preview",
"delete_project_scoped",
"project_work_dirs",
"set_project_dir",
"add_work_dir",
"remove_work_dir",
"list_pools",
"create_oauth_pool",
"create_apikey_pool",
"rename_pool",
"delete_pool",
"assign_pool",
"unassign_pool",
"set_terminal_config",
"project_icon",
"pool_label",
"todo_state",
"set_todo",
"usage_stats",
"stop_project",
"restart_project",
"start_or_focus_cmd",
"open_main_window",
"quit_app",
"sync_setting",
"set_sync_setting",
"terminal_font_size",
"set_terminal_font_size",
"link_pool_runtime",
"oauth_login",
"keychain_status",
"set_apikey",
"panel_archive_dir_cmd",
"panel_archive_cmd",
"set_archive_home_cmd",
"clear_archive_home_cmd",
"archive_docs_cmd",
"reveal_path_cmd",
"spellcheck_lang",
"open_terminal",
"term_start",
"term_log",
"term_write",
"term_resize",
"panel_read",
"commands_read",
"commands_delete",
"panel_set",
"search_read",
"search_run",
"panel_load",
"wiki_read",
"wiki_open",
"open_panel_window",
])),
)
.expect("tauri-build");
}
+58
View File
@@ -0,0 +1,58 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main",
"description": "Hauptfenster: komplette Projekt-/Pool-Verwaltung.",
"windows": [
"main"
],
"permissions": [
"core:default",
"core:window:allow-start-dragging",
"core:window:allow-set-size",
"core:window:allow-set-position",
"core:window:allow-outer-position",
"core:window:allow-inner-size",
"core:window:allow-minimize",
"core:window:allow-toggle-maximize",
"core:window:allow-close",
"core:window:allow-hide",
"core:window:allow-start-resize-dragging",
"autostart:default",
"dialog:default",
"clipboard-manager:allow-write-text",
"allow-list-projects",
"allow-create-project-full",
"allow-add-project",
"allow-project-work-dirs",
"allow-set-project-dir",
"allow-add-work-dir",
"allow-remove-work-dir",
"allow-list-pools",
"allow-create-oauth-pool",
"allow-create-apikey-pool",
"allow-rename-pool",
"allow-delete-pool",
"allow-assign-pool",
"allow-unassign-pool",
"allow-set-terminal-config",
"allow-project-icon",
"allow-todo-state",
"allow-set-todo",
"allow-usage-stats",
"allow-stop-project",
"allow-restart-project",
"allow-open-terminal",
"allow-sync-setting",
"allow-set-sync-setting",
"allow-link-pool-runtime",
"allow-oauth-login",
"allow-keychain-status",
"allow-set-apikey",
"allow-panel-archive-dir-cmd",
"allow-set-archive-home-cmd",
"allow-clear-archive-home-cmd",
"allow-archive-docs-cmd",
"allow-delete-preview",
"allow-delete-project-scoped"
]
}
+39
View File
@@ -0,0 +1,39 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "panel",
"description": "Abgelöstes Panel-Fenster: nur Panel-/Archiv-Kanäle, keine PTY, keine Verwaltung.",
"windows": [
"panel-*"
],
"permissions": [
"core:default",
"core:window:allow-start-dragging",
"core:window:allow-set-size",
"core:window:allow-set-position",
"core:window:allow-outer-position",
"core:window:allow-inner-size",
"core:window:allow-minimize",
"core:window:allow-toggle-maximize",
"core:window:allow-close",
"core:window:allow-hide",
"core:window:allow-start-resize-dragging",
"autostart:default",
"dialog:default",
"clipboard-manager:allow-write-text",
"allow-commands-delete",
"allow-commands-read",
"allow-list-projects",
"allow-panel-archive-cmd",
"allow-panel-archive-dir-cmd",
"allow-panel-load",
"allow-panel-read",
"allow-panel-set",
"allow-reveal-path-cmd",
"allow-search-read",
"allow-search-run",
"allow-spellcheck-lang",
"allow-term-log",
"allow-wiki-open",
"allow-wiki-read"
]
}
@@ -1,12 +1,9 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "enables the default permissions",
"identifier": "popup",
"description": "Tray-Popup: Projektliste anzeigen und starten.",
"windows": [
"main",
"popup",
"term-*",
"panel-*"
"popup"
],
"permissions": [
"core:default",
@@ -22,6 +19,11 @@
"core:window:allow-start-resize-dragging",
"autostart:default",
"dialog:default",
"clipboard-manager:allow-write-text"
"clipboard-manager:allow-write-text",
"allow-list-projects",
"allow-project-icon",
"allow-start-or-focus-cmd",
"allow-open-main-window",
"allow-quit-app"
]
}
+47
View File
@@ -0,0 +1,47 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "terminal",
"description": "Terminal-Fenster: PTY und angedocktes Panel.",
"windows": [
"term-*"
],
"permissions": [
"core:default",
"core:window:allow-start-dragging",
"core:window:allow-set-size",
"core:window:allow-set-position",
"core:window:allow-outer-position",
"core:window:allow-inner-size",
"core:window:allow-minimize",
"core:window:allow-toggle-maximize",
"core:window:allow-close",
"core:window:allow-hide",
"core:window:allow-start-resize-dragging",
"autostart:default",
"dialog:default",
"clipboard-manager:allow-write-text",
"allow-commands-delete",
"allow-commands-read",
"allow-list-projects",
"allow-open-panel-window",
"allow-panel-archive-cmd",
"allow-panel-archive-dir-cmd",
"allow-panel-load",
"allow-panel-read",
"allow-panel-set",
"allow-pool-label",
"allow-project-icon",
"allow-reveal-path-cmd",
"allow-search-read",
"allow-search-run",
"allow-set-terminal-font-size",
"allow-spellcheck-lang",
"allow-term-log",
"allow-term-resize",
"allow-term-start",
"allow-term-write",
"allow-terminal-font-size",
"allow-wiki-open",
"allow-wiki-read"
]
}
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-add-project"
description = "Enables the add_project command without any pre-configured scope."
commands.allow = ["add_project"]
[[permission]]
identifier = "deny-add-project"
description = "Denies the add_project command without any pre-configured scope."
commands.deny = ["add_project"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-add-work-dir"
description = "Enables the add_work_dir command without any pre-configured scope."
commands.allow = ["add_work_dir"]
[[permission]]
identifier = "deny-add-work-dir"
description = "Denies the add_work_dir command without any pre-configured scope."
commands.deny = ["add_work_dir"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-archive-docs-cmd"
description = "Enables the archive_docs_cmd command without any pre-configured scope."
commands.allow = ["archive_docs_cmd"]
[[permission]]
identifier = "deny-archive-docs-cmd"
description = "Denies the archive_docs_cmd command without any pre-configured scope."
commands.deny = ["archive_docs_cmd"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-assign-pool"
description = "Enables the assign_pool command without any pre-configured scope."
commands.allow = ["assign_pool"]
[[permission]]
identifier = "deny-assign-pool"
description = "Denies the assign_pool command without any pre-configured scope."
commands.deny = ["assign_pool"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-clear-archive-home-cmd"
description = "Enables the clear_archive_home_cmd command without any pre-configured scope."
commands.allow = ["clear_archive_home_cmd"]
[[permission]]
identifier = "deny-clear-archive-home-cmd"
description = "Denies the clear_archive_home_cmd command without any pre-configured scope."
commands.deny = ["clear_archive_home_cmd"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-commands-delete"
description = "Enables the commands_delete command without any pre-configured scope."
commands.allow = ["commands_delete"]
[[permission]]
identifier = "deny-commands-delete"
description = "Denies the commands_delete command without any pre-configured scope."
commands.deny = ["commands_delete"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-commands-read"
description = "Enables the commands_read command without any pre-configured scope."
commands.allow = ["commands_read"]
[[permission]]
identifier = "deny-commands-read"
description = "Denies the commands_read command without any pre-configured scope."
commands.deny = ["commands_read"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-create-apikey-pool"
description = "Enables the create_apikey_pool command without any pre-configured scope."
commands.allow = ["create_apikey_pool"]
[[permission]]
identifier = "deny-create-apikey-pool"
description = "Denies the create_apikey_pool command without any pre-configured scope."
commands.deny = ["create_apikey_pool"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-create-oauth-pool"
description = "Enables the create_oauth_pool command without any pre-configured scope."
commands.allow = ["create_oauth_pool"]
[[permission]]
identifier = "deny-create-oauth-pool"
description = "Denies the create_oauth_pool command without any pre-configured scope."
commands.deny = ["create_oauth_pool"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-create-project-full"
description = "Enables the create_project_full command without any pre-configured scope."
commands.allow = ["create_project_full"]
[[permission]]
identifier = "deny-create-project-full"
description = "Denies the create_project_full command without any pre-configured scope."
commands.deny = ["create_project_full"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-delete-pool"
description = "Enables the delete_pool command without any pre-configured scope."
commands.allow = ["delete_pool"]
[[permission]]
identifier = "deny-delete-pool"
description = "Denies the delete_pool command without any pre-configured scope."
commands.deny = ["delete_pool"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-delete-preview"
description = "Enables the delete_preview command without any pre-configured scope."
commands.allow = ["delete_preview"]
[[permission]]
identifier = "deny-delete-preview"
description = "Denies the delete_preview command without any pre-configured scope."
commands.deny = ["delete_preview"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-delete-project"
description = "Enables the delete_project command without any pre-configured scope."
commands.allow = ["delete_project"]
[[permission]]
identifier = "deny-delete-project"
description = "Denies the delete_project command without any pre-configured scope."
commands.deny = ["delete_project"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-delete-project-scoped"
description = "Enables the delete_project_scoped command without any pre-configured scope."
commands.allow = ["delete_project_scoped"]
[[permission]]
identifier = "deny-delete-project-scoped"
description = "Denies the delete_project_scoped command without any pre-configured scope."
commands.deny = ["delete_project_scoped"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-keychain-status"
description = "Enables the keychain_status command without any pre-configured scope."
commands.allow = ["keychain_status"]
[[permission]]
identifier = "deny-keychain-status"
description = "Denies the keychain_status command without any pre-configured scope."
commands.deny = ["keychain_status"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-link-pool-runtime"
description = "Enables the link_pool_runtime command without any pre-configured scope."
commands.allow = ["link_pool_runtime"]
[[permission]]
identifier = "deny-link-pool-runtime"
description = "Denies the link_pool_runtime command without any pre-configured scope."
commands.deny = ["link_pool_runtime"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-list-pools"
description = "Enables the list_pools command without any pre-configured scope."
commands.allow = ["list_pools"]
[[permission]]
identifier = "deny-list-pools"
description = "Denies the list_pools command without any pre-configured scope."
commands.deny = ["list_pools"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-list-projects"
description = "Enables the list_projects command without any pre-configured scope."
commands.allow = ["list_projects"]
[[permission]]
identifier = "deny-list-projects"
description = "Denies the list_projects command without any pre-configured scope."
commands.deny = ["list_projects"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-oauth-login"
description = "Enables the oauth_login command without any pre-configured scope."
commands.allow = ["oauth_login"]
[[permission]]
identifier = "deny-oauth-login"
description = "Denies the oauth_login command without any pre-configured scope."
commands.deny = ["oauth_login"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-open-main-window"
description = "Enables the open_main_window command without any pre-configured scope."
commands.allow = ["open_main_window"]
[[permission]]
identifier = "deny-open-main-window"
description = "Denies the open_main_window command without any pre-configured scope."
commands.deny = ["open_main_window"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-open-panel-window"
description = "Enables the open_panel_window command without any pre-configured scope."
commands.allow = ["open_panel_window"]
[[permission]]
identifier = "deny-open-panel-window"
description = "Denies the open_panel_window command without any pre-configured scope."
commands.deny = ["open_panel_window"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-open-terminal"
description = "Enables the open_terminal command without any pre-configured scope."
commands.allow = ["open_terminal"]
[[permission]]
identifier = "deny-open-terminal"
description = "Denies the open_terminal command without any pre-configured scope."
commands.deny = ["open_terminal"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-panel-archive-cmd"
description = "Enables the panel_archive_cmd command without any pre-configured scope."
commands.allow = ["panel_archive_cmd"]
[[permission]]
identifier = "deny-panel-archive-cmd"
description = "Denies the panel_archive_cmd command without any pre-configured scope."
commands.deny = ["panel_archive_cmd"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-panel-archive-dir-cmd"
description = "Enables the panel_archive_dir_cmd command without any pre-configured scope."
commands.allow = ["panel_archive_dir_cmd"]
[[permission]]
identifier = "deny-panel-archive-dir-cmd"
description = "Denies the panel_archive_dir_cmd command without any pre-configured scope."
commands.deny = ["panel_archive_dir_cmd"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-panel-load"
description = "Enables the panel_load command without any pre-configured scope."
commands.allow = ["panel_load"]
[[permission]]
identifier = "deny-panel-load"
description = "Denies the panel_load command without any pre-configured scope."
commands.deny = ["panel_load"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-panel-read"
description = "Enables the panel_read command without any pre-configured scope."
commands.allow = ["panel_read"]
[[permission]]
identifier = "deny-panel-read"
description = "Denies the panel_read command without any pre-configured scope."
commands.deny = ["panel_read"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-panel-set"
description = "Enables the panel_set command without any pre-configured scope."
commands.allow = ["panel_set"]
[[permission]]
identifier = "deny-panel-set"
description = "Denies the panel_set command without any pre-configured scope."
commands.deny = ["panel_set"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-pool-label"
description = "Enables the pool_label command without any pre-configured scope."
commands.allow = ["pool_label"]
[[permission]]
identifier = "deny-pool-label"
description = "Denies the pool_label command without any pre-configured scope."
commands.deny = ["pool_label"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-project-icon"
description = "Enables the project_icon command without any pre-configured scope."
commands.allow = ["project_icon"]
[[permission]]
identifier = "deny-project-icon"
description = "Denies the project_icon command without any pre-configured scope."
commands.deny = ["project_icon"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-project-work-dirs"
description = "Enables the project_work_dirs command without any pre-configured scope."
commands.allow = ["project_work_dirs"]
[[permission]]
identifier = "deny-project-work-dirs"
description = "Denies the project_work_dirs command without any pre-configured scope."
commands.deny = ["project_work_dirs"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-quit-app"
description = "Enables the quit_app command without any pre-configured scope."
commands.allow = ["quit_app"]
[[permission]]
identifier = "deny-quit-app"
description = "Denies the quit_app command without any pre-configured scope."
commands.deny = ["quit_app"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-remove-project"
description = "Enables the remove_project command without any pre-configured scope."
commands.allow = ["remove_project"]
[[permission]]
identifier = "deny-remove-project"
description = "Denies the remove_project command without any pre-configured scope."
commands.deny = ["remove_project"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-remove-work-dir"
description = "Enables the remove_work_dir command without any pre-configured scope."
commands.allow = ["remove_work_dir"]
[[permission]]
identifier = "deny-remove-work-dir"
description = "Denies the remove_work_dir command without any pre-configured scope."
commands.deny = ["remove_work_dir"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-rename-pool"
description = "Enables the rename_pool command without any pre-configured scope."
commands.allow = ["rename_pool"]
[[permission]]
identifier = "deny-rename-pool"
description = "Denies the rename_pool command without any pre-configured scope."
commands.deny = ["rename_pool"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-restart-project"
description = "Enables the restart_project command without any pre-configured scope."
commands.allow = ["restart_project"]
[[permission]]
identifier = "deny-restart-project"
description = "Denies the restart_project command without any pre-configured scope."
commands.deny = ["restart_project"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-reveal-path-cmd"
description = "Enables the reveal_path_cmd command without any pre-configured scope."
commands.allow = ["reveal_path_cmd"]
[[permission]]
identifier = "deny-reveal-path-cmd"
description = "Denies the reveal_path_cmd command without any pre-configured scope."
commands.deny = ["reveal_path_cmd"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-search-read"
description = "Enables the search_read command without any pre-configured scope."
commands.allow = ["search_read"]
[[permission]]
identifier = "deny-search-read"
description = "Denies the search_read command without any pre-configured scope."
commands.deny = ["search_read"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-search-run"
description = "Enables the search_run command without any pre-configured scope."
commands.allow = ["search_run"]
[[permission]]
identifier = "deny-search-run"
description = "Denies the search_run command without any pre-configured scope."
commands.deny = ["search_run"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-set-apikey"
description = "Enables the set_apikey command without any pre-configured scope."
commands.allow = ["set_apikey"]
[[permission]]
identifier = "deny-set-apikey"
description = "Denies the set_apikey command without any pre-configured scope."
commands.deny = ["set_apikey"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-set-archive-home-cmd"
description = "Enables the set_archive_home_cmd command without any pre-configured scope."
commands.allow = ["set_archive_home_cmd"]
[[permission]]
identifier = "deny-set-archive-home-cmd"
description = "Denies the set_archive_home_cmd command without any pre-configured scope."
commands.deny = ["set_archive_home_cmd"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-set-project-dir"
description = "Enables the set_project_dir command without any pre-configured scope."
commands.allow = ["set_project_dir"]
[[permission]]
identifier = "deny-set-project-dir"
description = "Denies the set_project_dir command without any pre-configured scope."
commands.deny = ["set_project_dir"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-set-sync-setting"
description = "Enables the set_sync_setting command without any pre-configured scope."
commands.allow = ["set_sync_setting"]
[[permission]]
identifier = "deny-set-sync-setting"
description = "Denies the set_sync_setting command without any pre-configured scope."
commands.deny = ["set_sync_setting"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-set-terminal-config"
description = "Enables the set_terminal_config command without any pre-configured scope."
commands.allow = ["set_terminal_config"]
[[permission]]
identifier = "deny-set-terminal-config"
description = "Denies the set_terminal_config command without any pre-configured scope."
commands.deny = ["set_terminal_config"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-set-terminal-font-size"
description = "Enables the set_terminal_font_size command without any pre-configured scope."
commands.allow = ["set_terminal_font_size"]
[[permission]]
identifier = "deny-set-terminal-font-size"
description = "Denies the set_terminal_font_size command without any pre-configured scope."
commands.deny = ["set_terminal_font_size"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-set-todo"
description = "Enables the set_todo command without any pre-configured scope."
commands.allow = ["set_todo"]
[[permission]]
identifier = "deny-set-todo"
description = "Denies the set_todo command without any pre-configured scope."
commands.deny = ["set_todo"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-spellcheck-lang"
description = "Enables the spellcheck_lang command without any pre-configured scope."
commands.allow = ["spellcheck_lang"]
[[permission]]
identifier = "deny-spellcheck-lang"
description = "Denies the spellcheck_lang command without any pre-configured scope."
commands.deny = ["spellcheck_lang"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-start-or-focus-cmd"
description = "Enables the start_or_focus_cmd command without any pre-configured scope."
commands.allow = ["start_or_focus_cmd"]
[[permission]]
identifier = "deny-start-or-focus-cmd"
description = "Denies the start_or_focus_cmd command without any pre-configured scope."
commands.deny = ["start_or_focus_cmd"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-stop-project"
description = "Enables the stop_project command without any pre-configured scope."
commands.allow = ["stop_project"]
[[permission]]
identifier = "deny-stop-project"
description = "Denies the stop_project command without any pre-configured scope."
commands.deny = ["stop_project"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-sync-setting"
description = "Enables the sync_setting command without any pre-configured scope."
commands.allow = ["sync_setting"]
[[permission]]
identifier = "deny-sync-setting"
description = "Denies the sync_setting command without any pre-configured scope."
commands.deny = ["sync_setting"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-term-log"
description = "Enables the term_log command without any pre-configured scope."
commands.allow = ["term_log"]
[[permission]]
identifier = "deny-term-log"
description = "Denies the term_log command without any pre-configured scope."
commands.deny = ["term_log"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-term-resize"
description = "Enables the term_resize command without any pre-configured scope."
commands.allow = ["term_resize"]
[[permission]]
identifier = "deny-term-resize"
description = "Denies the term_resize command without any pre-configured scope."
commands.deny = ["term_resize"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-term-start"
description = "Enables the term_start command without any pre-configured scope."
commands.allow = ["term_start"]
[[permission]]
identifier = "deny-term-start"
description = "Denies the term_start command without any pre-configured scope."
commands.deny = ["term_start"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-term-write"
description = "Enables the term_write command without any pre-configured scope."
commands.allow = ["term_write"]
[[permission]]
identifier = "deny-term-write"
description = "Denies the term_write command without any pre-configured scope."
commands.deny = ["term_write"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-terminal-font-size"
description = "Enables the terminal_font_size command without any pre-configured scope."
commands.allow = ["terminal_font_size"]
[[permission]]
identifier = "deny-terminal-font-size"
description = "Denies the terminal_font_size command without any pre-configured scope."
commands.deny = ["terminal_font_size"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-todo-state"
description = "Enables the todo_state command without any pre-configured scope."
commands.allow = ["todo_state"]
[[permission]]
identifier = "deny-todo-state"
description = "Denies the todo_state command without any pre-configured scope."
commands.deny = ["todo_state"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-unassign-pool"
description = "Enables the unassign_pool command without any pre-configured scope."
commands.allow = ["unassign_pool"]
[[permission]]
identifier = "deny-unassign-pool"
description = "Denies the unassign_pool command without any pre-configured scope."
commands.deny = ["unassign_pool"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-usage-stats"
description = "Enables the usage_stats command without any pre-configured scope."
commands.allow = ["usage_stats"]
[[permission]]
identifier = "deny-usage-stats"
description = "Denies the usage_stats command without any pre-configured scope."
commands.deny = ["usage_stats"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-wiki-open"
description = "Enables the wiki_open command without any pre-configured scope."
commands.allow = ["wiki_open"]
[[permission]]
identifier = "deny-wiki-open"
description = "Denies the wiki_open command without any pre-configured scope."
commands.deny = ["wiki_open"]
@@ -0,0 +1,11 @@
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-wiki-read"
description = "Enables the wiki_read command without any pre-configured scope."
commands.allow = ["wiki_read"]
[[permission]]
identifier = "deny-wiki-read"
description = "Denies the wiki_read command without any pre-configured scope."
commands.deny = ["wiki_read"]
+2 -2
View File
@@ -247,8 +247,8 @@ fn invoke_handlers() -> impl Fn(tauri::ipc::Invoke<tauri::Wry>) -> bool + Send +
commands::list_projects,
commands::create_project_full,
commands::add_project,
commands::remove_project,
commands::delete_project,
commands::delete_preview,
commands::delete_project_scoped,
commands::project_work_dirs,
commands::set_project_dir,
commands::add_work_dir,
+20 -17
View File
@@ -13,12 +13,12 @@ use crate::domain::pool::{
link_pool_runtime_in, list_pools_in, read_pool, rename_pool_in, set_apikey_in, PoolInfo,
};
use crate::domain::project::{
add_project_in, add_work_dir_in, assign_pool_in, create_project_full_in, delete_project_in,
display_name_in, is_running, kill_terminals, list_projects_in, project_work_dirs_in,
read_project_config_in, remove_work_dir_in, resolve_icon_path, running_projects_using_pool,
set_project_dir_in, set_terminal_config_in, unassign_pool_in, Project, TerminalConfig,
add_project_in, add_work_dir_in, assign_pool_in, create_project_full_in, delete_preview_in,
delete_project_scoped_in, display_name_in, is_running, kill_terminals, list_projects_in,
project_work_dirs_in, read_project_config_in, remove_work_dir_in, resolve_icon_path,
running_projects_using_pool, set_project_dir_in, set_terminal_config_in, unassign_pool_in,
DeletePreview, Project, TerminalConfig,
};
use crate::domain::registry::unregister_project;
use crate::domain::settings;
use crate::domain::todo::{set_todo_in, todo_state_in};
use crate::domain::usage::{usage_stats_in, UsageRow};
@@ -60,16 +60,6 @@ pub(crate) fn add_project(path: String) -> Result<(), String> {
add_project_in(&Paths::real(), &path)
}
/// Projekt aus der Registry nehmen; der Ordner bleibt unangetastet.
#[tauri::command]
pub(crate) fn remove_project(project: String) -> Result<(), String> {
if is_running(&project) {
let name = display_name_in(&Paths::real(), &project)?;
return Err(format!("{name} läuft noch — erst beenden"));
}
unregister_project(&Paths::real(), &project)
}
/// Projektordner neu zuordnen; bei laufender Session gesperrt.
#[tauri::command]
pub(crate) fn set_project_dir(project: String, dir: String) -> Result<(), String> {
@@ -90,13 +80,26 @@ pub(crate) fn remove_work_dir(project: String, dir: String) -> Result<(), String
remove_work_dir_in(&Paths::real(), &project, &dir)
}
/// Artefakt-Vorschau für den Lösch-Dialog.
#[tauri::command]
pub(crate) fn delete_project(project: String, delete_work_dirs: bool) -> Result<(), String> {
pub(crate) fn delete_preview(project: String) -> Result<DeletePreview, String> {
delete_preview_in(&Paths::real(), &project)
}
/// Löschen in drei Stufen: "integration" (nur ai-control-Spuren),
/// "archive" (zusätzlich Archiv), "full" (zusätzlich Projektordner,
/// Arbeitsordner per Flag).
#[tauri::command]
pub(crate) fn delete_project_scoped(
project: String,
scope: String,
delete_work_dirs: bool,
) -> Result<(), String> {
if is_running(&project) {
let name = display_name_in(&Paths::real(), &project)?;
return Err(format!("{name} läuft noch — erst beenden"));
}
delete_project_in(&Paths::real(), &project, delete_work_dirs)
delete_project_scoped_in(&Paths::real(), &project, &scope, delete_work_dirs)
}
#[tauri::command]
+180 -19
View File
@@ -499,27 +499,125 @@ pub(crate) fn project_work_dirs_in(paths: &Paths, name: &str) -> Result<Vec<Stri
)
}
/// Löscht den Projektordner und den Registry-Eintrag; optional auch die
/// verknüpften Arbeitsordner. Bei laufender Session wird abgebrochen.
pub(crate) fn delete_project_in(
/// Vorschau für den Lösch-Dialog: welche Artefakte die drei Stufen
/// (Integration / Integration+Archiv / komplett) konkret treffen würden.
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct DeletePreview {
pub(crate) name: String,
pub(crate) project_dir: String,
pub(crate) ai_control_dir: bool,
pub(crate) archive_permission: bool,
pub(crate) todo_hook: bool,
pub(crate) panel_files: usize,
pub(crate) archive_home: Option<String>,
pub(crate) archive_docs: usize,
pub(crate) work_dirs: Vec<String>,
}
pub(crate) fn delete_preview_in(paths: &Paths, project: &str) -> Result<DeletePreview, String> {
check_name(project)?;
let dir = project_dir(paths, project)?;
let cfg = read_project_config_in(paths, project)?;
let archive_docs = match cfg.archive_home.as_deref() {
Some(a) => {
let home = expand_home(paths, a);
if home.is_dir() {
crate::domain::archive_index::scan_archive(&home)?.len()
} else {
0
}
}
None => 0,
};
Ok(DeletePreview {
name: cfg.name.clone().unwrap_or_else(|| project.to_string()),
project_dir: contract_home(paths, &dir),
ai_control_dir: dir.join(PROJECT_CONFIG_DIR).is_dir(),
archive_permission: cfg.archive_home.is_some(),
todo_hook: crate::domain::todo::todo_state_in(paths, project)?,
panel_files: session_files(paths, project).iter().filter(|f| f.is_file()).count(),
archive_home: cfg.archive_home.clone(),
archive_docs,
work_dirs: project_work_dirs_in(paths, project)?,
})
}
/// Flüchtige Panel-Kanaldateien des Projekts unter ~/.config/ai-control/panels.
fn session_files(paths: &Paths, project: &str) -> [PathBuf; 4] {
let panels = paths.config_dir().join("panels");
[
panels.join(format!("{project}.md")),
panels.join(format!("{project}.commands.jsonl")),
panels.join(format!("{project}.search.json")),
panels.join(format!("{project}.wiki.json")),
]
}
/// Löscht ein Projekt in drei Stufen (Eskalationsleiter, jede schließt die
/// vorige ein):
/// - "integration": nur die ai-control-Spuren — Registry, .ai-control/,
/// Archiv-Rechte und Todo-Hook in der settings.json, Panel-Dateien,
/// .desktop. Ordner, memory/ und das Claude-Code-Grundgerüst der
/// settings.json bleiben.
/// - "archive": zusätzlich der Archiv-Ordner samt Dokumenten.
/// - "full": zusätzlich der Projektordner; Arbeitsordner per Flag.
pub(crate) fn delete_project_scoped_in(
paths: &Paths,
name: &str,
project: &str,
scope: &str,
delete_work_dirs: bool,
) -> Result<(), String> {
check_name(name)?;
let dir = project_dir(paths, name)?;
check_name(project)?;
let dir = project_dir(paths, project)?;
if !dir.is_dir() {
return Err(format!("Projekt nicht gefunden: {name}"));
return Err(format!("Projektordner nicht gefunden: {}", dir.display()));
}
if delete_work_dirs {
for wd in project_work_dirs_in(paths, name)? {
let wd_path = expand_home(paths, &wd);
fs::remove_dir_all(&wd_path).map_err(|e| format!("{}: {e}", wd_path.display()))?;
let cfg = read_project_config_in(paths, project)?;
// Archiv vor der Integration räumen — Stufe 1 löscht die Config, die den
// Pfad kennt.
if scope == "archive" || scope == "full" {
if let Some(a) = cfg.archive_home.as_deref() {
let home = expand_home(paths, a);
if home.is_dir() {
fs::remove_dir_all(&home).map_err(|e| format!("{}: {e}", home.display()))?;
}
}
}
fs::remove_dir_all(&dir).map_err(|e| format!("{}: {e}", dir.display()))?;
crate::domain::registry::unregister_project(paths, name)?;
crate::platform::remove_terminal_desktop(paths, name);
match scope {
"integration" | "archive" => {
if let Some(a) = cfg.archive_home.as_deref() {
crate::domain::archive::remove_archive_permission(paths, project, a)?;
}
if crate::domain::todo::todo_state_in(paths, project)? {
crate::domain::todo::set_todo_in(paths, project, false)?;
}
let ac = dir.join(PROJECT_CONFIG_DIR);
if ac.is_dir() {
fs::remove_dir_all(&ac).map_err(|e| format!("{}: {e}", ac.display()))?;
}
}
"full" => {
if delete_work_dirs {
for wd in project_work_dirs_in(paths, project)? {
let wd_path = expand_home(paths, &wd);
fs::remove_dir_all(&wd_path).map_err(|e| format!("{}: {e}", wd_path.display()))?;
}
}
fs::remove_dir_all(&dir).map_err(|e| format!("{}: {e}", dir.display()))?;
}
other => return Err(format!("unbekannter Lösch-Umfang: {other}")),
}
for f in session_files(paths, project) {
if f.is_file() {
fs::remove_file(&f).map_err(|e| format!("{}: {e}", f.display()))?;
}
}
crate::domain::registry::unregister_project(paths, project)?;
crate::platform::remove_terminal_desktop(paths, project);
Ok(())
}
@@ -1149,7 +1247,7 @@ mod tests {
fn projekt_loeschen() {
let p = tmp_paths();
create_project(&p, "proj").unwrap();
delete_project_in(&p, "proj", false).unwrap();
delete_project_scoped_in(&p, "proj", "full", false).unwrap();
assert!(!p.projects_dir().join("proj").exists());
}
@@ -1158,7 +1256,7 @@ mod tests {
let p = tmp_paths();
let id = create_project_full_in(&p, "proj", None, None, Some("~/projects/proj"), true, TerminalConfig::default(), false)
.unwrap();
delete_project_in(&p, &id, false).unwrap();
delete_project_scoped_in(&p, &id, "full", false).unwrap();
assert!(!p.projects_dir().join("proj").exists());
assert!(p.home.join("projects").join("proj").is_dir());
}
@@ -1168,7 +1266,7 @@ mod tests {
let p = tmp_paths();
let id = create_project_full_in(&p, "proj", None, None, Some("~/projects/proj"), true, TerminalConfig::default(), false)
.unwrap();
delete_project_in(&p, &id, true).unwrap();
delete_project_scoped_in(&p, &id, "full", true).unwrap();
assert!(!p.projects_dir().join("proj").exists());
assert!(!p.home.join("projects").join("proj").exists());
}
@@ -1179,7 +1277,7 @@ mod tests {
let id = create_project_full_in(&p, "proj", None, None, Some("~/projects/proj"), true, TerminalConfig::default(), false)
.unwrap();
fs::remove_dir_all(p.home.join("projects").join("proj")).unwrap();
assert!(delete_project_in(&p, &id, true).is_err());
assert!(delete_project_scoped_in(&p, &id, "full", true).is_err());
}
#[test]
@@ -1276,9 +1374,72 @@ mod tests {
assert!(read_for_panel_in(&p, "proj", "/dev/zero").is_err());
}
/// Stufe „nur Integration": ai-control-Spuren weg, Ordner und
/// Claude-Code-Bestand bleiben.
#[test]
fn loeschen_nur_integration() {
let p = tmp_paths();
let id = create_project_full_in(&p, "proj", None, None, None, false, TerminalConfig::default(), true)
.unwrap();
let dir = p.projects_dir().join("proj");
// Archiv-Home samt Permission wie über die UI gesetzt
let mut cfg = read_project_config_in(&p, &id).unwrap();
cfg.archive_home = Some("~/archiv/proj".into());
write_project_config_in(&p, &id, &cfg).unwrap();
crate::domain::archive::add_archive_permission(&p, &id, "~/archiv/proj").unwrap();
fs::create_dir_all(p.home.join("archiv/proj")).unwrap();
fs::write(p.home.join("archiv/proj/doc.md"), "inhalt").unwrap();
// flüchtige Panel-Datei
fs::create_dir_all(p.config_dir().join("panels")).unwrap();
fs::write(p.config_dir().join("panels").join(format!("{id}.md")), "x").unwrap();
let preview = delete_preview_in(&p, &id).unwrap();
assert!(preview.ai_control_dir && preview.archive_permission && preview.todo_hook);
assert_eq!(preview.panel_files, 1);
assert_eq!(preview.archive_docs, 1);
delete_project_scoped_in(&p, &id, "integration", false).unwrap();
assert!(dir.is_dir());
assert!(!dir.join(PROJECT_CONFIG_DIR).exists());
let sp = fs::read_to_string(settings_path(&dir)).unwrap();
assert!(!sp.contains("archiv/proj"));
assert!(!sp.contains("OFFENE-PUNKTE.md")); // Hook weg (leere Hook-Liste darf bleiben)
assert!(sp.contains("autoMemoryDirectory")); // Claude-Code-Bestand bleibt
assert!(dir.join(crate::domain::todo::TODO_FILE).is_file()); // Nutzerinhalt bleibt
assert!(p.home.join("archiv/proj/doc.md").is_file()); // Archiv bleibt
assert!(!p.config_dir().join("panels").join(format!("{id}.md")).exists());
assert!(load_registry(&p).unwrap().is_empty());
}
/// Stufe „Integration & Archiv": zusätzlich stirbt der Archiv-Ordner.
#[test]
fn loeschen_integration_und_archiv() {
let p = tmp_paths();
let id = create_project_full_in(&p, "proj", None, None, None, false, TerminalConfig::default(), false)
.unwrap();
let mut cfg = read_project_config_in(&p, &id).unwrap();
cfg.archive_home = Some("~/archiv/proj".into());
write_project_config_in(&p, &id, &cfg).unwrap();
fs::create_dir_all(p.home.join("archiv/proj")).unwrap();
delete_project_scoped_in(&p, &id, "archive", false).unwrap();
assert!(p.projects_dir().join("proj").is_dir());
assert!(!p.home.join("archiv/proj").exists());
assert!(load_registry(&p).unwrap().is_empty());
}
#[test]
fn loeschen_unbekannter_umfang_scheitert() {
let p = tmp_paths();
create_project(&p, "proj").unwrap();
assert!(delete_project_scoped_in(&p, "proj", "halb", false).is_err());
}
#[test]
fn projekt_loeschen_unbekannt_scheitert() {
let p = tmp_paths();
assert!(delete_project_in(&p, "gibtsnicht", false).is_err());
assert!(delete_project_scoped_in(&p, "gibtsnicht", "full", false).is_err());
}
}
+2 -1
View File
@@ -13,7 +13,8 @@
"macOSPrivateApi": true,
"windows": [],
"security": {
"csp": null
"csp": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src ipc: http://ipc.localhost",
"devCsp": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src ipc: http://ipc.localhost ws://localhost:1420 http://localhost:1420"
}
},
"bundle": {
+3 -1
View File
@@ -201,7 +201,8 @@ onMounted(refresh);
<p v-if="error" class="error">{{ error }}</p>
<table v-if="pools.length" class="grid">
<div v-if="pools.length" class="list-scroll">
<table class="grid">
<colgroup>
<col />
<col class="col-type" />
@@ -268,6 +269,7 @@ onMounted(refresh);
</tr>
</tbody>
</table>
</div>
<p v-else class="empty">{{ $t("pools.empty") }}</p>
<div v-if="dialog" class="overlay" @click.self="closeDialog">
+122 -40
View File
@@ -379,10 +379,25 @@ async function createProject() {
}
}
interface DeletePreview {
name: string;
projectDir: string;
aiControlDir: boolean;
archivePermission: boolean;
todoHook: boolean;
panelFiles: number;
archiveHome: string | null;
archiveDocs: number;
workDirs: string[];
}
// Eskalationsleiter: jede Stufe schließt die vorige ein.
type DeleteScope = "integration" | "archive" | "full";
interface PendingDelete {
id: string;
name: string;
workDirs: string[];
preview: DeletePreview;
scope: DeleteScope;
deleteWorkDirs: boolean;
}
@@ -391,10 +406,8 @@ const pendingDelete = ref<PendingDelete | null>(null);
async function askDelete(p: Project) {
error.value = "";
try {
const workDirs = await invoke<string[]>("project_work_dirs", {
project: p.id,
});
pendingDelete.value = { id: p.id, name: p.name, workDirs, deleteWorkDirs: false };
const preview = await invoke<DeletePreview>("delete_preview", { project: p.id });
pendingDelete.value = { id: p.id, preview, scope: "integration", deleteWorkDirs: false };
} catch (e) {
error.value = String(e);
}
@@ -403,9 +416,10 @@ async function askDelete(p: Project) {
async function confirmDelete() {
const d = pendingDelete.value!;
try {
await invoke("delete_project", {
await invoke("delete_project_scoped", {
project: d.id,
deleteWorkDirs: d.deleteWorkDirs,
scope: d.scope,
deleteWorkDirs: d.scope === "full" && d.deleteWorkDirs,
});
pendingDelete.value = null;
await refresh();
@@ -415,19 +429,6 @@ async function confirmDelete() {
}
}
// Nur den Registry-Eintrag entfernen; der Ordner bleibt.
async function unlinkProject() {
const d = pendingDelete.value!;
try {
await invoke("remove_project", { project: d.id });
pendingDelete.value = null;
await refresh();
} catch (e) {
error.value = String(e);
pendingDelete.value = null;
}
}
let timer: number;
onMounted(() => {
refresh();
@@ -447,6 +448,7 @@ onUnmounted(() => window.clearInterval(timer));
</div>
<p v-if="error" class="error">{{ error }}</p>
<div class="list-scroll">
<table class="grid">
<colgroup>
<col class="col-dot" />
@@ -516,6 +518,7 @@ onUnmounted(() => window.clearInterval(timer));
</tr>
</tbody>
</table>
</div>
<div v-if="wizard" class="overlay" @click.self="wizard = null">
<form class="dialog" @submit.prevent="createProject">
@@ -619,30 +622,76 @@ onUnmounted(() => window.clearInterval(timer));
<div v-if="pendingDelete" class="overlay" @click.self="pendingDelete = null">
<div class="dialog">
<h3>{{ $t("projects.deleteTitle", { name: pendingDelete.name }) }}</h3>
<p class="hint">{{ $t("projects.deleteWarning", { name: pendingDelete.name }) }}</p>
<template v-if="pendingDelete.workDirs.length">
<label class="checkline">
<input v-model="pendingDelete.deleteWorkDirs" type="checkbox" />
{{ $t("projects.deleteWorkDirs") }}
</label>
<ul class="affected">
<li v-for="wd in pendingDelete.workDirs" :key="wd">{{ wd }}</li>
</ul>
<p v-if="!pendingDelete.deleteWorkDirs" class="hint">
{{ $t("projects.workDirsStay") }}
</p>
</template>
<p class="hint">{{ $t("projects.unlinkHint") }}</p>
<h3>{{ $t("projects.deleteTitle", { name: pendingDelete.preview.name }) }}</h3>
<label class="scope">
<input v-model="pendingDelete.scope" type="radio" value="integration" />
<span>
<strong>{{ $t("projects.scopeIntegration") }}</strong>
<small>{{ $t("projects.scopeIntegrationDesc") }}</small>
</span>
</label>
<label class="scope">
<input
v-model="pendingDelete.scope"
type="radio"
value="archive"
:disabled="!pendingDelete.preview.archiveHome"
/>
<span>
<strong>{{ $t("projects.scopeArchive") }}</strong>
<small v-if="pendingDelete.preview.archiveHome">
{{ $t("projects.scopeArchiveDesc", { path: pendingDelete.preview.archiveHome }) }}
({{ $t("projects.docCount", pendingDelete.preview.archiveDocs) }})
</small>
<small v-else>{{ $t("projects.archiveNone") }}</small>
</span>
</label>
<label class="scope">
<input v-model="pendingDelete.scope" type="radio" value="full" />
<span>
<strong>{{ $t("projects.scopeFull") }}</strong>
<small>{{ $t("projects.scopeFullDesc", { path: pendingDelete.preview.projectDir }) }}</small>
</span>
</label>
<label
v-if="pendingDelete.scope === 'full' && pendingDelete.preview.workDirs.length"
class="checkline"
>
<input v-model="pendingDelete.deleteWorkDirs" type="checkbox" />
{{ $t("projects.deleteWorkDirs") }}
</label>
<p class="hint">{{ $t("projects.deletePreviewTitle") }}</p>
<ul class="affected">
<li>{{ $t("projects.artRegistry") }}</li>
<li v-if="pendingDelete.preview.aiControlDir">{{ $t("projects.artAiControl") }}</li>
<li v-if="pendingDelete.preview.archivePermission">{{ $t("projects.artArchivePerm") }}</li>
<li v-if="pendingDelete.preview.todoHook">{{ $t("projects.artTodoHook") }}</li>
<li v-if="pendingDelete.preview.panelFiles">
{{ $t("projects.artPanelFiles", pendingDelete.preview.panelFiles) }}
</li>
<li>{{ $t("projects.artDesktop") }}</li>
<li v-if="pendingDelete.scope !== 'integration' && pendingDelete.preview.archiveHome">
{{ $t("projects.artArchive", { path: pendingDelete.preview.archiveHome }) }}
({{ $t("projects.docCount", pendingDelete.preview.archiveDocs) }})
</li>
<li v-if="pendingDelete.scope === 'full'">
{{ $t("projects.artProjectDir", { path: pendingDelete.preview.projectDir }) }}
</li>
<template v-if="pendingDelete.scope === 'full' && pendingDelete.deleteWorkDirs">
<li v-for="wd in pendingDelete.preview.workDirs" :key="wd">
{{ $t("projects.artWorkDir", { path: wd }) }}
</li>
</template>
</ul>
<div class="actions">
<button type="button" @click="pendingDelete = null">
{{ $t("projects.cancel") }}
</button>
<button @click="unlinkProject">
{{ $t("projects.unlink") }}
</button>
<button class="danger" @click="confirmDelete">
{{ $t("projects.delete") }}
{{ $t("projects.deleteConfirm") }}
</button>
</div>
</div>
@@ -784,6 +833,39 @@ onUnmounted(() => window.clearInterval(timer));
</template>
<style scoped>
/* Lösch-Dialog: drei Stufen als Radio-Zeilen mit Beschreibung. */
.scope {
display: flex;
align-items: flex-start;
gap: 0.6rem;
cursor: pointer;
}
.scope input {
margin-top: 0.2rem;
}
.scope span {
display: flex;
flex-direction: column;
gap: 0.1rem;
}
.scope strong {
font-size: 0.9rem;
color: var(--text);
}
.scope small {
color: var(--overlay);
font-size: 0.78rem;
}
.scope:has(input:disabled) {
opacity: 0.55;
cursor: default;
}
/* Settings-Dialog: festes Grid Label | Wert | Aktionen in drei Gruppen. */
.settings-dialog {
position: relative;
+3 -1
View File
@@ -113,7 +113,8 @@ function cost(n: number): string {
<p v-if="error" class="error">{{ error }}</p>
<table v-if="groups.length" class="grid">
<div v-if="groups.length" class="list-scroll">
<table class="grid">
<colgroup>
<col />
<col class="col-num" />
@@ -164,5 +165,6 @@ function cost(n: number): string {
</tr>
</tfoot>
</table>
</div>
<p v-else-if="!loading" class="empty">{{ $t("usage.empty") }}</p>
</template>
+38 -12
View File
@@ -71,13 +71,26 @@ const de = {
delete: "Projekt löschen",
deleteRunning: "Läuft noch — erst beenden",
deleteTitle: "{name} löschen",
deleteWarning:
"Der Projektordner (inkl. memory/, Settings und Pool-Zuordnung) wird endgültig gelöscht.",
deleteConfirm: "Entfernen",
deleteWorkDirs: "Arbeitsordner mitlöschen:",
workDirsStay: "Die Arbeitsordner bleiben unangetastet.",
unlink: "Nur aus Liste nehmen",
unlinkHint:
"Nur aus Liste nehmen entfernt den Eintrag; der Ordner bleibt unangetastet.",
scopeIntegration: "Nur Integration entfernen",
scopeIntegrationDesc:
"ai-control-Spuren werden entfernt; Projektordner, memory/ und Todoliste bleiben.",
scopeArchive: "Integration & Archiv",
scopeArchiveDesc: "zusätzlich {path} — endgültig.",
scopeFull: "Projekt komplett löschen",
scopeFullDesc: "zusätzlich der Projektordner {path} — endgültig.",
deletePreviewTitle: "Wird entfernt:",
docCount: "kein Dokument | 1 Dokument | {count} Dokumente",
artRegistry: "Registry-Eintrag",
artAiControl: ".ai-control/ (Config + Icon)",
artArchivePerm: "Archiv-Berechtigung in .claude/settings.json",
artTodoHook: "Todo-Hook in .claude/settings.json",
artPanelFiles: "Panel-Kanaldatei | {count} Panel-Kanaldateien",
artDesktop: ".desktop-Starter",
artArchive: "Archiv {path}",
artProjectDir: "Projektordner {path}",
artWorkDir: "Arbeitsordner {path}",
todo: "Todoliste",
todoDesc: "OFFENE-PUNKTE.md bei jedem Sessionstart einspielen",
groupAppearance: "Darstellung",
@@ -201,13 +214,26 @@ const en: typeof de = {
delete: "Delete project",
deleteRunning: "Still running — stop it first",
deleteTitle: "Delete {name}",
deleteWarning:
"The project folder (incl. memory/, settings and pool assignment) will be deleted permanently.",
deleteConfirm: "Remove",
deleteWorkDirs: "Also delete working folders:",
workDirsStay: "The working folders stay untouched.",
unlink: "Remove from list only",
unlinkHint:
"Remove from list only drops the entry; the folder stays untouched.",
scopeIntegration: "Remove integration only",
scopeIntegrationDesc:
"ai-control traces are removed; project folder, memory/ and todo list stay.",
scopeArchive: "Integration & archive",
scopeArchiveDesc: "additionally {path} — permanent.",
scopeFull: "Delete project completely",
scopeFullDesc: "additionally the project folder {path} — permanent.",
deletePreviewTitle: "Will be removed:",
docCount: "no documents | 1 document | {count} documents",
artRegistry: "registry entry",
artAiControl: ".ai-control/ (config + icon)",
artArchivePerm: "archive permission in .claude/settings.json",
artTodoHook: "todo hook in .claude/settings.json",
artPanelFiles: "panel channel file | {count} panel channel files",
artDesktop: ".desktop launcher",
artArchive: "archive {path}",
artProjectDir: "project folder {path}",
artWorkDir: "working folder {path}",
todo: "Todo list",
todoDesc: "Inject OFFENE-PUNKTE.md at every session start",
groupAppearance: "Appearance",
+11 -1
View File
@@ -123,10 +123,20 @@ input[type="checkbox"] {
main {
flex: 1;
overflow-y: auto;
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
padding: 1.5rem;
}
/* Tab-Inhalte: Toolbar und Fehlerzeile stehen, nur die Liste scrollt. */
.list-scroll {
flex: 1;
min-height: 0;
overflow-y: auto;
}
/* ---------- Controls ---------- */
button,