From 9b509a2aaefa08e9993ca90c24a6b72f3d6b6f21 Mon Sep 17 00:00:00 2001 From: "marcus.hinz" Date: Sun, 26 Jul 2026 22:44:28 +0200 Subject: [PATCH] Archiv-Formular mit Titel-Feld; Panel-Fenster einzeilige Overlay-Titelbar; Header name@pool ohne Chip - Archivieren: Titel-Feld, vorbelegt mit der ersten Ueberschrift (panel_title_cmd), bestimmt Frontmatter-title und Dateinamen-Slug; archive_panel (MCP) nimmt title ebenfalls - Panel-/Archiv-Fenster (macOS): TitleBarStyle::Overlay + hidden_title, Ampel in der Topbar (84px), eigener Schliessen-Knopf dort aus - Terminal-Header: Pool direkt am Projektnamen als name@pool, Chip und Pipe entfernt --- src-tauri/src/app.rs | 1 + src-tauri/src/commands.rs | 8 ++++++++ src-tauri/src/domain/archive.rs | 10 +++++++++- src-tauri/src/mcp.rs | 7 +++++++ src-tauri/src/terminal.rs | 7 +++++++ src/archive-form.ts | 14 +++++++++++++- src/messages.ts | 4 ++++ src/panel-window.css | 8 ++++++++ src/panel.ts | 2 ++ src/terminal-window.css | 13 ++++++++----- src/terminal.ts | 2 ++ terminal.html | 2 +- 12 files changed, 70 insertions(+), 8 deletions(-) diff --git a/src-tauri/src/app.rs b/src-tauri/src/app.rs index 442e57d..db0f922 100644 --- a/src-tauri/src/app.rs +++ b/src-tauri/src/app.rs @@ -288,6 +288,7 @@ fn invoke_handlers() -> impl Fn(tauri::ipc::Invoke) -> bool + Send + commands::set_apikey, commands::panel_archive_dir_cmd, commands::panel_archive_cmd, + commands::panel_title_cmd, commands::set_archive_home_cmd, commands::change_archive_home_cmd, commands::clear_archive_home_cmd, diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 6626228..7596dc4 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -448,6 +448,7 @@ pub(crate) fn clear_archive_home_cmd(project: String) -> Result<(), String> { pub(crate) fn panel_archive_cmd( project: String, dir: Option, + title: Option, folder: Option, description: Option, tags: Option>, @@ -470,6 +471,7 @@ pub(crate) fn panel_archive_cmd( None => None, }; let meta = ArchiveMeta { + title: title.filter(|t| !t.trim().is_empty()), folder, description: description.filter(|d| !d.trim().is_empty()), tags: tags.unwrap_or_default(), @@ -477,6 +479,12 @@ pub(crate) fn panel_archive_cmd( archive_panel_content(&project, dir.as_deref(), &meta).map(|p| p.display().to_string()) } +/// Titel-Vorbelegung fürs Archiv-Formular: erste Überschrift des Entwurfs. +#[tauri::command] +pub(crate) fn panel_title_cmd(project: String) -> String { + crate::domain::archive::panel_title(&project) +} + /// Dokumentliste des Archiv-Index (frisch gescannt) fürs Panel. #[tauri::command] pub(crate) fn archive_docs_cmd( diff --git a/src-tauri/src/domain/archive.rs b/src-tauri/src/domain/archive.rs index 1c5f716..5a951bd 100644 --- a/src-tauri/src/domain/archive.rs +++ b/src-tauri/src/domain/archive.rs @@ -180,6 +180,8 @@ pub(crate) fn remove_archive_permission( /// Metadaten beim Archivieren: Unterordner im Archiv-Home plus Frontmatter-Felder. #[derive(Default)] pub(crate) struct ArchiveMeta { + /// Titel aus dem Archiv-Formular; ohne ihn gilt die erste Überschrift. + pub(crate) title: Option, /// Unterordner relativ zum Archiv-Home (wird angelegt). pub(crate) folder: Option, /// Einzeiler fürs Frontmatter. @@ -220,7 +222,7 @@ pub(crate) fn archive_panel_content( .map_err(|e| e.to_string())? .as_secs(); let (stamp, iso) = utc_stamp(secs); - let title = first_line(&text); + let title = meta.title.clone().unwrap_or_else(|| first_line(&text)); let (path, mut file) = create_unique(&dir, &stamp, &slugify(&title))?; // Frontmatter trägt den Anzeigenamen, nicht die Projekt-ID. let name = crate::domain::project::display_name_in(&Paths::real(), project)?; @@ -365,6 +367,12 @@ pub(crate) fn strip_stamp(stem: &str) -> &str { } /// Titelzeile: erste Überschrift (## …) oder sonst erste nicht-leere Zeile. +/// Titel des aktuellen Panel-Entwurfs (erste Überschrift bzw. erste Zeile) — +/// Vorbelegung für das Titel-Feld im Archiv-Formular. +pub(crate) fn panel_title(project: &str) -> String { + first_line(&fs::read_to_string(panel_file(project)).unwrap_or_default()) +} + fn first_line(text: &str) -> String { let mut fallback: Option<&str> = None; for line in text.lines() { diff --git a/src-tauri/src/mcp.rs b/src-tauri/src/mcp.rs index 8043ef4..8463f78 100644 --- a/src-tauri/src/mcp.rs +++ b/src-tauri/src/mcp.rs @@ -223,6 +223,12 @@ fn tool_def(name: &str) -> Value { "inputSchema": { "type": "object", "properties": { + "title": { + "type": "string", + "description": + "Titel des Dokuments; ohne ihn gilt die erste Überschrift \ + des Entwurfs.", + }, "folder": { "type": "string", "description": @@ -516,6 +522,7 @@ fn call_archive(req: &Value) -> Value { let project = std::env::var("AI_CONTROL_PROJECT").unwrap_or_default(); let args = &req["params"]["arguments"]; let meta = crate::domain::archive::ArchiveMeta { + title: args["title"].as_str().map(str::to_string), folder: args["folder"].as_str().map(str::to_string), description: args["description"].as_str().map(str::to_string), tags: args["tags"] diff --git a/src-tauri/src/terminal.rs b/src-tauri/src/terminal.rs index fc1e406..9df2092 100644 --- a/src-tauri/src/terminal.rs +++ b/src-tauri/src/terminal.rs @@ -811,6 +811,13 @@ pub async fn open_panel_window( None => builder.center(), }; + // macOS: Titelbar als Overlay über der eigenen Kopfleiste — eine Zeile, + // wie im Terminal-Fenster; die Topbar in panel.html ist Drag-Region. + #[cfg(target_os = "macos")] + let builder = builder + .title_bar_style(tauri::TitleBarStyle::Overlay) + .hidden_title(true); + // Linux/GNOME: keine GTK-Deko — eigene Kopfleiste in panel.html, wie beim // Terminal-Fenster. #[cfg(target_os = "linux")] diff --git a/src/archive-form.ts b/src/archive-form.ts index 415615a..e5f892a 100644 --- a/src/archive-form.ts +++ b/src/archive-form.ts @@ -9,6 +9,7 @@ import { t } from "./messages"; export interface ArchiveFormMeta { + title?: string; folder?: string; description?: string; tags: string[]; @@ -26,6 +27,8 @@ export interface ArchiveFolderNode { export interface ArchiveFormOptions { /// Vorhandene Archiv-Ordner (Pfad + Titel) für den Zielordner-Baum. folders?(): Promise; + /// Titel-Vorbelegung (erste Überschrift des Entwurfs), geladen beim Öffnen. + title?(): Promise; /// „Auf Platte legen": Entwurf als Datei an frei gewähltem Pfad ablegen. onSave?(): void; } @@ -72,6 +75,8 @@ export function initArchiveForm( caption.textContent = t("archiveForm.title"); box.append(caption); + const title = field(t("archiveForm.titleLabel"), t("archiveForm.docTitle")); + // --- Zielordner: durchlaufbarer Baum ------------------------------------ // Logische Sicht des Archivs (Knotentitel plus Pfad an der Zeile), startet // eingeklappt. Die gewählte Zeile ist die Anzeige des Ziels — kein zweites @@ -231,6 +236,7 @@ export function initArchiveForm( document.body.append(form); const meta = (): ArchiveFormMeta => ({ + title: title.value.trim() || undefined, folder: selected || undefined, description: desc.value.trim() || undefined, tags: tags.value @@ -242,6 +248,7 @@ export function initArchiveForm( const close = () => { form.hidden = true; selected = ""; + title.value = ""; desc.value = ""; tags.value = ""; document.removeEventListener("keydown", onKey); @@ -254,7 +261,12 @@ export function initArchiveForm( const open = () => { form.hidden = false; document.addEventListener("keydown", onKey); - desc.focus(); + title.focus(); + // Vorbelegung: erste Überschrift des Entwurfs, änderbar vor der Übernahme. + void opts.title?.().then((v) => { + title.value = v; + title.select(); + }); // Frisch laden: zwischen zwei Dialogen können Ordner entstanden sein. // Scheitert das Laden, steht der Grund im Kasten — ein leerer Kasten // ohne Erklärung war der Fehler der Vorversion. diff --git a/src/messages.ts b/src/messages.ts index 05ca40f..f3cbdbe 100644 --- a/src/messages.ts +++ b/src/messages.ts @@ -227,6 +227,8 @@ const de = { }, archiveForm: { title: "Entwurf archivieren", + titleLabel: "Titel", + docTitle: "Titel im Archiv", folderLabel: "Zielordner im Archiv", expand: "Auf-/zuklappen", descriptionLabel: "Beschreibung", @@ -513,6 +515,8 @@ const en: typeof de = { }, archiveForm: { title: "Archive draft", + titleLabel: "Title", + docTitle: "Title in the archive", folderLabel: "Target folder in the archive", expand: "Expand/collapse", descriptionLabel: "Description", diff --git a/src/panel-window.css b/src/panel-window.css index 7bbb0a6..0ac64d5 100644 --- a/src/panel-window.css +++ b/src/panel-window.css @@ -107,6 +107,14 @@ body { :root[data-platform="mac"] .winbtn.osctl { display: none; } +/* macOS: Ampel liegt als Overlay links in der Topbar — Platz lassen, + eigenen Schließen-Knopf aus (wie im Terminal-Header). */ +:root[data-platform="mac"] .panel-topbar { + padding-left: 84px; +} +:root[data-platform="mac"] .panel-topbar .winbtn.close { + display: none; +} /* Resize-Zonen für das dekorationslose Fenster (nur Linux). */ #resize-grips { display: none; diff --git a/src/panel.ts b/src/panel.ts index 95bcc8e..4aa1edc 100644 --- a/src/panel.ts +++ b/src/panel.ts @@ -134,6 +134,7 @@ const archiveForm = initArchiveForm( await invoke("panel_archive_cmd", { project, dir, + title: meta.title ?? null, folder: meta.folder ?? null, description: meta.description ?? null, tags: meta.tags, @@ -148,6 +149,7 @@ const archiveForm = initArchiveForm( // Ordner-Vorschläge aus dem Archiv; „Auf Platte legen" schreibt den // Entwurf an einen frei gewählten Pfad (ohne Archiv, ohne Frontmatter). folders: () => invoke("archive_folders", { project }), + title: () => invoke("panel_title_cmd", { project }), onSave: async () => { try { const { save } = await import("@tauri-apps/plugin-dialog"); diff --git a/src/terminal-window.css b/src/terminal-window.css index c5351bc..fb8ce1c 100644 --- a/src/terminal-window.css +++ b/src/terminal-window.css @@ -86,12 +86,15 @@ header .panel-tabs[hidden] { margin: 0 12px; align-self: center; } -#pool { - background: var(--line); +/* Pool als schlichter Anhang: name@pool, ohne Chip; die negativen + Ränder heben das Header-gap (10px) auf — bündig am Namen. */ +#pool-sep { color: var(--muted); - border-radius: 999px; - padding: 4px 12px; - font-size: 11px; + margin-left: -10px; +} +#pool { + color: var(--muted); + margin-left: -10px; } /* Eigene Fensterknöpfe (Linux): rechtsbündig, volle Header-Höhe. */ #winbtns { diff --git a/src/terminal.ts b/src/terminal.ts index 2773482..7999469 100644 --- a/src/terminal.ts +++ b/src/terminal.ts @@ -304,6 +304,7 @@ const archiveForm = initArchiveForm( await invoke("panel_archive_cmd", { project, dir, + title: meta.title ?? null, folder: meta.folder ?? null, description: meta.description ?? null, tags: meta.tags, @@ -319,6 +320,7 @@ const archiveForm = initArchiveForm( // Ordner-Vorschläge aus dem Archiv; „Auf Platte legen" schreibt den // Entwurf an einen frei gewählten Pfad (ohne Archiv, ohne Frontmatter). folders: () => invoke("archive_folders", { project }), + title: () => invoke("panel_title_cmd", { project }), onSave: async () => { try { const { save } = await import("@tauri-apps/plugin-dialog"); diff --git a/terminal.html b/terminal.html index bff2554..026c55a 100644 --- a/terminal.html +++ b/terminal.html @@ -8,7 +8,7 @@
- + @