diff --git a/.planning/quick/260917-eta-desktop-client-tray-eintrag-beenden-been/260917-eta-PLAN.md b/.planning/quick/260917-eta-desktop-client-tray-eintrag-beenden-been/260917-eta-PLAN.md new file mode 100644 index 0000000..7372c35 --- /dev/null +++ b/.planning/quick/260917-eta-desktop-client-tray-eintrag-beenden-been/260917-eta-PLAN.md @@ -0,0 +1,150 @@ +--- +phase: quick-260917-eta +plan: 01 +type: execute +wave: 1 +depends_on: [] +autonomous: true +requirements: [QUICK-260917-ETA] + +files_modified: + - apps/desktop/src-tauri/src/lib.rs + - CHANGELOG.md + +estimate: + tokens: 14000 + raw_tokens: 14000 + tasks: 2 + confidence: low + +must_haves: + truths: + - "Tray-Menue → „Beenden“ beendet die Desktop-App: der Run-Handler in `apps/desktop/src-tauri/src/lib.rs` verhindert `RunEvent::ExitRequested` nur noch, wenn `code` `None` ist (letztes Fenster vom Nutzer geschlossen); ein programmatischer `app.exit(0)` aus dem Tray-Handler „quit“ (`code: Some(0)`) laeuft durch. Umgesetzt als Muster `RunEvent::ExitRequested { code: None, api, .. }`." + - "Tray-Menue → „Öffnen“ und Linksklick auf das Tray-Symbol holen ein minimiertes Fenster zurueck: in beiden Handlern steht `let _ = w.unminimize();` unmittelbar VOR `let _ = w.show();` (Tauri 2.11.3 `WebviewWindow::unminimize`, `webview_window.rs` Z. 1984)." + - "`cargo check` und `cargo clippy` in `apps/desktop/src-tauri` enden beide mit `Finished` und ohne eine Zeile, die mit `warning` beginnt (Baseline vor dem Fix: 0 Warnungen, Cache ist warm — check ~2 s, clippy ~3 s)." + - "Gegenueber Basis-Commit `280aab6` ist unter `apps/` ausschliesslich `apps/desktop/src-tauri/src/lib.rs` veraendert; ausserhalb von `apps/` und `.planning/` ausschliesslich `CHANGELOG.md`. Kein `tauri build`, kein Docker, kein Testserver, kein `git push`." + - "CHANGELOG.md, `## Unveröffentlicht` → `### Behoben`: genau zwei neue Stichpunkte direkt nach dem Icon-Stichpunkt (`… statt des Tessera-T …`), je genau einmal in der Datei, Stil wie im Bestand (typografische Anfuehrungszeichen „…“, echte Umlaute, kein Punkt am Ende, kein Fliesstext)." + - "Zwei Commits: `fix(desktop): …` (nur lib.rs) und `docs: …` (nur CHANGELOG.md)." + artifacts: + - "apps/desktop/src-tauri/src/lib.rs — Run-Handler mit `code: None`-Muster; `w.unminimize()` in den Handlern „open“ und Tray-Linksklick; deutscher Kommentar am Run-Handler" + - "CHANGELOG.md — zwei neue Stichpunkte unter Unveröffentlicht/Behoben" + key_links: + - "`app.exit(0)` im Tray-Handler „quit“ (lib.rs Z. 174-176) loest `RunEvent::ExitRequested { code: Some(0), .. }` aus (Tauri 2.11.3 `app.rs` Z. 225-232: `code` ist `None` bei Nutzer-Interaktion, `Some` bei `AppHandle::exit`/`restart`). Der bisherige Run-Handler (Z. 241-245) rief `api.prevent_exit()` bedingungslos — deshalb lief `tessera-desktop.exe` nach „Beenden“ weiter. Das Muster `code: None` ist die einzige Aenderung, die diesen Weg freigibt, ohne das Weiterlaufen im Infobereich beim Fenster-Schliessen aufzugeben." + - "`on_window_event` (Z. 232-237) faengt `CloseRequested` mit `hide()` + `prevent_close()` ab — bleibt unveraendert; das ist der Weg, ueber den die App im Infobereich weiterlaeuft." + - "`show()` + `set_focus()` allein stellen ein per Win+D minimiertes Fenster unter Windows nicht wieder her; `unminimize()` (SW_RESTORE) muss davor stehen. Der Linksklick-Handler in `on_tray_icon_event` (Z. 179-191) ist Code-identisch mit „open“ und bekommt dieselbe Zeile, sonst bleibt der Fehler auf diesem zweiten Weg bestehen." + - "`apps/web/src/lib/changelog.test.ts` arbeitet mit eingebettetem Beispieltext, nicht mit der echten CHANGELOG.md — der scoped `grep`-Nachweis in Task 2 ist die eigentliche Pruefung der Eintraege." +--- + + +Zwei Fehler im Tray-Verhalten des Desktop-Clients (Phase 18) beheben, beide in `apps/desktop/src-tauri/src/lib.rs`: + +1. „Beenden“ im Infobereich-Menue beendet die App nicht. Der Handler in `app.run(...)` ruft bei jedem `RunEvent::ExitRequested` bedingungslos `api.prevent_exit()` — gedacht fuer das Weiterlaufen im Infobereich beim Schliessen des Fensters, blockiert aber auch den ausdruecklichen `app.exit(0)` aus dem Tray-Handler „quit“. Fix: nur bei `code: None` (Nutzer-Interaktion) verhindern, `Some(..)` (programmatisch) durchlassen. +2. „Öffnen“ im Infobereich-Menue (und der Linksklick auf das Symbol) holen ein minimiertes Fenster nicht zurueck (Win+D, dann „Öffnen“: nichts sichtbar). Fix: `w.unminimize()` vor `w.show()`. + +Dazu zwei Stichpunkte im CHANGELOG. Beide Befunde sind auf der Windows-Test-VM reproduziert — nicht erneut untersuchen; der Orchestrator prueft den Fix anschliessend selbst auf der VM. + +Purpose: Das Tray-Menue der Desktop-App muss tun, was draufsteht — Beenden beendet, Öffnen zeigt das Fenster. +Output: Geaenderte `lib.rs` (check/clippy gruen, 0 Warnungen), zwei CHANGELOG-Stichpunkte, zwei Commits. + + + +@~/.claude/gsd-core/workflows/execute-plan.md +@~/.claude/gsd-core/templates/summary.md + + + +@/home/vicolab/projects/tessera-ctl/CLAUDE.md +@/home/vicolab/projects/tessera-ctl/apps/desktop/src-tauri/src/lib.rs +@/home/vicolab/projects/tessera-ctl/CHANGELOG.md + + + + + + Task 1: Run-Handler laesst app.exit() durch; „Öffnen“/Linksklick rufen unminimize() vor show() + apps/desktop/src-tauri/src/lib.rs + + - apps/desktop/src-tauri/src/lib.rs Z. 143-178 (Tray-Menue-Handler: „open“ Z. 144-149, „quit“ Z. 174-176 mit `app.exit(0)`), Z. 179-191 (Linksklick-Handler in `on_tray_icon_event`, Code-identisch mit „open“), Z. 232-237 (`on_window_event`, bleibt unveraendert), Z. 241-245 (Run-Handler, der Fehler) + - Kommentarstil im Bestand: Deutsch, Umlaute als ae/oe/ue, mit Verweis auf den Grund (z. B. Z. 24-28, Z. 109-111, Z. 204-208) + + + Die Befunde sind verifiziert — nichts untersuchen, nur die drei Stellen aendern. Keine neuen `use`-Zeilen noetig (`RunEvent` ist bereits importiert, `unminimize` ist eine Methode von `WebviewWindow`). + + Vorab `git status --porcelain apps/desktop/src-tauri/src/lib.rs` pruefen: Zum Planungszeitpunkt lag genau diese Aenderung bereits UNCOMMITTET im Arbeitsbaum (` M`, Diff gegen `280aab6`: `code: None`-Muster mit fuenfzeiligem Kommentar im Run-Handler, `let _ = w.unminimize();` in beiden Handlern, einzeiliger Kommentar ueber der Zeile im „open“-Handler) — das Gate lief damit bereits auf `RUST-OK`. Ist das noch so, die Schritte 1-3 NICHT erneut anwenden (sonst doppelte Zeilen), sondern den Diff gegen die Schritte 1-3 gegenlesen, das Gate laufen lassen und committen. Ist der Arbeitsbaum sauber, die Schritte 1-3 ausfuehren. + + 1. Run-Handler (Z. 241-245): Das `if let`-Muster von `RunEvent::ExitRequested { api, .. }` auf `RunEvent::ExitRequested { code: None, api, .. }` aendern; der Rumpf bleibt `api.prevent_exit();`. Damit greift der Schutz nur noch, wenn der Exit durch Nutzer-Interaktion angefordert wird (letztes Fenster geschlossen, `code` ist `None`), waehrend ein programmatischer `app.exit(0)` aus dem Tray-Handler „quit“ (`code: Some(0)`) durchlaeuft. Bewusst als Muster `code: None` statt eines verschachtelten `if code.is_none()` — kuerzer, kein zweites Einrueckungsniveau, clippy-sauber. Direkt ueber dem `if let` einen deutschen Kommentar (Stil wie im Bestand, zwei bis vier Zeilen) ergaenzen, der erklaert: Fenster schliessen → `code` `None` → App laeuft im Infobereich weiter; Tray-Eintrag „Beenden“ ruft `app.exit(0)` → `code` `Some` → muss durchgelassen werden, sonst bleibt der Prozess samt Tray-Symbol stehen (Tauri 2.11.3, `app.rs` `RunEvent::ExitRequested`). Der Kommentar wiederholt die Aufruf-Syntax `api.prevent_exit()` nicht woertlich (das Gate zaehlt diese Zeichenkette genau einmal). + + 2. Tray-Menue-Handler „open“ (Z. 144-149): Innerhalb des `if let Some(w) = app.get_webview_window("main")` als ERSTE Zeile `let _ = w.unminimize();` einfuegen, unmittelbar vor `let _ = w.show();`. `show()` und `set_focus()` bleiben in ihrer Reihenfolge. Reihenfolge ist Absicht: `unminimize` entspricht unter Windows SW_RESTORE und holt ein per Win+D minimiertes Fenster zurueck, was `show()` (SW_SHOW) allein nicht tut. Ein kurzer deutscher Kommentar (eine Zeile) ueber der neuen Zeile ist erwuenscht, ohne die Aufruf-Syntax `w.unminimize()` woertlich zu wiederholen. + + 3. Linksklick-Handler in `on_tray_icon_event` (Z. 186-189): Dieselbe Zeile `let _ = w.unminimize();` als erste Zeile innerhalb von `if let Some(w) = tray.app_handle().get_webview_window("main")`, unmittelbar vor `let _ = w.show();`. Begruendung: der Handler ist Code-identisch mit „open“ und hat denselben Fehler; nur „open“ zu fixen liesse den zweiten Weg zum Fenster kaputt. Kein weiterer Kommentar noetig (der Kommentar aus Schritt 2 gilt sinngemaess; wer will, verweist mit einem Halbsatz darauf). + + Nichts sonst anfassen: `on_window_event` (Z. 232-237), das Menue, die Versionspruefung, `Cargo.toml`, `Cargo.lock`, `tauri.conf.json` bleiben unveraendert. `cargo fmt` ist erlaubt, darf aber keine anderen Zeilen umformatieren (Bestand ist bereits rustfmt-konform; wenn `cargo fmt` etwas anderes anfasst, die Aenderung zuruecknehmen). + + Danach im Verzeichnis `apps/desktop/src-tauri`: `cargo check` und `cargo clippy` (Standardprofil, ohne Zusatzflags — genau so laeuft es auch in `.gitea/workflows/ci.yml` Z. 131-132). Beide muessen mit `Finished` enden und duerfen keine Zeile ausgeben, die mit `warning` beginnt. Der Zielordner ist warm (check ~2 s, clippy ~3 s). Kein `tauri build`, kein Docker. + + Commit nach gruenem Gate, nur `apps/desktop/src-tauri/src/lib.rs`: `fix(desktop): Tray „Beenden“ beendet die App (ExitRequested nur bei code None verhindern); „Öffnen“/Linksklick holen minimiertes Fenster per unminimize zurück` + + + cd /home/vicolab/projects/tessera-ctl/apps/desktop/src-tauri && grep -q 'RunEvent::ExitRequested { code: None, api, .. }' src/lib.rs && [ "$(grep -n 'api.prevent_exit()' src/lib.rs | wc -l)" = 1 ] && grep -B1 'api.prevent_exit()' src/lib.rs | grep -q 'code: None' && [ "$(grep -n 'let _ = w.unminimize();' src/lib.rs | wc -l)" = 2 ] && [ "$(grep -A1 'let _ = w.unminimize();' src/lib.rs | grep -n 'let _ = w.show();' | wc -l)" = 2 ] && grep -q 'app.exit(0)' src/lib.rs && grep -q 'api.prevent_close()' src/lib.rs && D="$(git -C /home/vicolab/projects/tessera-ctl diff --name-only 280aab6 -- apps)" && [ "$D" = "apps/desktop/src-tauri/src/lib.rs" ] && C="$(cargo check 2>&1)" && printf '%s\n' "$C" | tail -1 | grep -q Finished && ! printf '%s\n' "$C" | grep -q '^warning' && L="$(cargo clippy 2>&1)" && printf '%s\n' "$L" | tail -1 | grep -q Finished && ! printf '%s\n' "$L" | grep -q '^warning' && echo RUST-OK + + Gate druckt `RUST-OK` (laeuft vor UND nach dem Commit gleich, Baseline ist `280aab6`): das `code: None`-Muster steht im Run-Handler, `api.prevent_exit()` kommt auf genau einer Zeile vor und die Zeile davor enthaelt `code: None`, `let _ = w.unminimize();` steht auf genau zwei Zeilen und jeweils direkt vor `let _ = w.show();`, `app.exit(0)` im „quit“-Handler und `api.prevent_close()` in `on_window_event` sind unveraendert vorhanden, `git diff --name-only 280aab6 -- apps` liefert exakt `apps/desktop/src-tauri/src/lib.rs`, `cargo check` und `cargo clippy` enden mit `Finished` ohne `warning`-Zeile. Commit `fix(desktop): …` mit nur `lib.rs` erstellt. + + + + Task 2: Zwei CHANGELOG-Stichpunkte unter Unveröffentlicht / Behoben + CHANGELOG.md + + - CHANGELOG.md Z. 5-29 (`## Unveröffentlicht` mit den Rubriken Neu / Geändert / Entfernt / Behoben; Stil der Stichpunkte: „Bereich: kurzer Satz“, kein Punkt am Ende, echte Umlaute, typografische Anfuehrungszeichen „…“; Z. 28 ist der letzte Behoben-Stichpunkt zum Desktop-Symbol) + + + In CHANGELOG.md im Abschnitt `## Unveröffentlicht`, Rubrik `### Behoben` (derzeit vier Stichpunkte, Z. 25-28), direkt nach der Zeile `- Desktop-App: Symbol zeigte eine „1“ statt des Tessera-T – die gedrehte gelbe Kachel fehlte` genau diese zwei Zeilen in dieser Reihenfolge einfuegen: + + `- Desktop-App: „Beenden“ im Infobereich-Menü beendete die App nicht` + `- Desktop-App: „Öffnen“ im Infobereich-Menü und Klick auf das Symbol holten ein minimiertes Fenster nicht zurück` + + Typografische Anfuehrungszeichen „ und “ (U+201E / U+201C) wie im Bestand, echte Umlaute (Menü, zurück), kein Punkt am Ende, kein Fliesstext, keine weiteren Zeilen. Die Leerzeile vor `## 1.1.0 – 2026-09-16` bleibt erhalten. Keine anderen Rubriken oder Versionen anfassen, keine neue Rubrik anlegen. + + Commit, nur CHANGELOG.md: `docs: CHANGELOG – Tray „Beenden“/„Öffnen“ der Desktop-App unter Unveröffentlicht/Behoben` + + + cd /home/vicolab/projects/tessera-ctl && A='- Desktop-App: „Beenden“ im Infobereich-Menü beendete die App nicht' && B='- Desktop-App: „Öffnen“ im Infobereich-Menü und Klick auf das Symbol holten ein minimiertes Fenster nicht zurück' && S="$(sed -n '/^## Unveröffentlicht/,/^## 1\.1\.0/p' CHANGELOG.md | sed -n '/^### Behoben/,/^## /p')" && printf '%s\n' "$S" | grep -Fxq -e "$A" && printf '%s\n' "$S" | grep -Fxq -e "$B" && [ "$(grep -Fx -e "$A" CHANGELOG.md | wc -l)" = 1 ] && [ "$(grep -Fx -e "$B" CHANGELOG.md | wc -l)" = 1 ] && [ "$(grep -A2 -F 'statt des Tessera-T' CHANGELOG.md | sed -n '2p')" = "$A" ] && [ "$(grep -A2 -F 'statt des Tessera-T' CHANGELOG.md | sed -n '3p')" = "$B" ] && D="$(git diff --name-only 280aab6 -- . ':!apps' ':!.planning')" && [ "$D" = "CHANGELOG.md" ] && pnpm --filter @tessera/web exec vitest run src/lib/changelog.test.ts && echo CHANGELOG-GATE-OK + + Gate druckt `CHANGELOG-GATE-OK`: beide Stichpunkte stehen genau einmal in der Datei, innerhalb von `## Unveröffentlicht` → `### Behoben`, in dieser Reihenfolge unmittelbar nach dem Tessera-T-Stichpunkt; ausserhalb von `apps/` und `.planning/` ist gegenueber `280aab6` nur CHANGELOG.md veraendert; `changelog.test.ts` bleibt gruen (10 Tests, ca. 2 s). Commit `docs: …` mit nur CHANGELOG.md erstellt. Hinweis: `grep` braucht `-e "$A"`, weil die Zeile mit `- ` beginnt und sonst als Option gelesen wird. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| Tray-Menue → App-Lebenszyklus | Nutzer-Klick im Infobereich fuehrt zu `app.exit(0)`; der Run-Handler entscheidet, ob der Prozess endet | +| Betriebssystem → Fensterzustand | `unminimize`/`show`/`set_focus` sind lokale Fensteroperationen ohne Netzwerk oder Fremdeingabe | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | +|-----------|----------|-----------|----------|-------------|-----------------| +| T-eta-01 | Denial of Service | Run-Handler `RunEvent::ExitRequested` (lib.rs) | low | mitigate | Muster `code: None` statt bedingungslosem Verhindern — Fenster-Schliessen haelt die App weiterhin im Infobereich (`prevent_close` in `on_window_event` unveraendert, Gate prueft `api.prevent_close()`), waehrend „Beenden“ den Prozess sauber beendet; kein haengender Prozess mehr | +| T-eta-02 | Tampering | Repo-Umfang | low | mitigate | Gates pruefen per `git diff --name-only 280aab6`, dass unter `apps/` nur `lib.rs` und sonst nur `CHANGELOG.md` veraendert sind; `Cargo.lock`, `tauri.conf.json`, CI unangetastet | +| T-eta-SC | Tampering | npm/pip/cargo installs | low | accept | Keine Paketinstallation; keine Aenderung an `Cargo.toml`/`Cargo.lock`, `cargo check`/`clippy` arbeiten aus dem vorhandenen Registry-Cache | + + + +- Task-1-Gate `RUST-OK` und Task-2-Gate `CHANGELOG-GATE-OK` jeweils gruen. +- `git log --oneline -2` zeigt die beiden Commits (`fix(desktop): …`, `docs: CHANGELOG …`); `git status` danach sauber bis auf `.planning/`. +- Nicht Teil dieses Plans: `tauri build`, Docker-Build, Deploy, Testserver, `git push`. Die Verhaltenspruefung (Tray „Beenden“ beendet `tessera-desktop.exe`, „Öffnen“ nach Win+D zeigt das Fenster) macht der Orchestrator auf der Windows-VM. + + + +- Run-Handler verhindert `ExitRequested` nur bei `code: None`; `app.exit(0)` aus dem Tray laeuft durch. +- „open“-Handler und Tray-Linksklick rufen `unminimize()` vor `show()`. +- `cargo check` und `cargo clippy` gruen, 0 Warnungen. +- CHANGELOG.md hat unter `## Unveröffentlicht` → `### Behoben` genau die zwei neuen Stichpunkte. +- Zwei Commits, kein Push, keine weiteren Dateien veraendert. + + + +Create `/home/vicolab/projects/tessera-ctl/.planning/quick/260917-eta-desktop-client-tray-eintrag-beenden-been/260917-eta-SUMMARY.md` when done +