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
This commit is contained in:
marcus.hinz
2026-07-26 22:44:28 +02:00
parent 082422c32a
commit 9b509a2aae
12 changed files with 70 additions and 8 deletions
+1
View File
@@ -288,6 +288,7 @@ fn invoke_handlers() -> impl Fn(tauri::ipc::Invoke<tauri::Wry>) -> 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,
+8
View File
@@ -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<String>,
title: Option<String>,
folder: Option<String>,
description: Option<String>,
tags: Option<Vec<String>>,
@@ -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(
+9 -1
View File
@@ -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<String>,
/// Unterordner relativ zum Archiv-Home (wird angelegt).
pub(crate) folder: Option<String>,
/// 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() {
+7
View File
@@ -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"]
+7
View File
@@ -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")]
+13 -1
View File
@@ -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<ArchiveFolderNode[]>;
/// Titel-Vorbelegung (erste Überschrift des Entwurfs), geladen beim Öffnen.
title?(): Promise<string>;
/// „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.
+4
View File
@@ -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",
+8
View File
@@ -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;
+2
View File
@@ -134,6 +134,7 @@ const archiveForm = initArchiveForm(
await invoke<string>("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");
+8 -5
View File
@@ -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 {
+2
View File
@@ -304,6 +304,7 @@ const archiveForm = initArchiveForm(
await invoke<string>("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");
+1 -1
View File
@@ -8,7 +8,7 @@
<header data-tauri-drag-region>
<img id="project-icon" hidden data-tauri-drag-region />
<span id="project-name" data-tauri-drag-region></span>
<span class="tab-sep" id="pool-sep"></span>
<span id="pool-sep">@</span>
<span id="pool"></span>
<!-- Tabs kommen aus der Modul-Registry (panel-wiring). -->
<div class="panel-tabs" id="panel-tabs"></div>