Files
tessera-ctl/.planning/quick/260917-eta-desktop-client-tray-eintrag-beenden-been/260917-eta-PLAN.md
T

153 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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."
---
<objective>
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.
</objective>
<execution_context>
@~/.claude/gsd-core/workflows/execute-plan.md
@~/.claude/gsd-core/templates/summary.md
</execution_context>
<context>
@/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
</context>
<tasks>
<task type="auto">
<name>Task 1: Run-Handler laesst app.exit() durch; „Öffnen“/Linksklick rufen unminimize() vor show()</name>
<files>apps/desktop/src-tauri/src/lib.rs</files>
<read_first>
- 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)
</read_first>
<action>
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`).
IDEMPOTENZ ZUERST: `grep -n 'code: None' apps/desktop/src-tauri/src/lib.rs` ausfuehren. Liefert das einen Treffer, liegt der Fix bereits vor — zum Zeitpunkt der Planfreigabe war er schon als Commit `68a69c6` (`fix(desktop): Tray „Beenden“ …`) im Log, entstanden parallel zur Planung. Dann die Schritte 1-3 NICHT erneut anwenden (sonst doppelte Zeilen), sondern nur den Stand gegen die Schritte 1-3 gegenlesen, das Gate laufen lassen und KEINEN neuen Commit erzeugen (`git log --oneline -3 -- apps/desktop/src-tauri/src/lib.rs` zeigt den vorhandenen). Nur wenn `grep` keinen Treffer liefert, die Schritte 1-3 ausfuehren und wie unten committen.
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`
</action>
<verify>
<automated>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</automated>
</verify>
<done>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.</done>
</task>
<task type="auto">
<name>Task 2: Zwei CHANGELOG-Stichpunkte unter Unveröffentlicht / Behoben</name>
<files>CHANGELOG.md</files>
<read_first>
- 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)
</read_first>
<action>
IDEMPOTENZ ZUERST: `grep -n 'Infobereich-Menü' CHANGELOG.md` ausfuehren. Liefert das zwei Treffer, sind die Stichpunkte bereits da — zum Zeitpunkt der Planfreigabe schon als Commit `9ba7456` (`docs: CHANGELOG – Tray …`) im Log, entstanden parallel zur Planung. Dann nichts einfuegen, nur das Gate laufen lassen und KEINEN neuen Commit erzeugen. Nur bei null Treffern wie folgt vorgehen:
In CHANGELOG.md im Abschnitt `## Unveröffentlicht`, Rubrik `### Behoben` (vor dem Fix 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`
</action>
<verify>
<automated>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</automated>
</verify>
<done>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.</done>
</task>
</tasks>
<threat_model>
## 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 |
</threat_model>
<verification>
- 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.
</verification>
<success_criteria>
- 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.
</success_criteria>
<output>
Create `/home/vicolab/projects/tessera-ctl/.planning/quick/260917-eta-desktop-client-tray-eintrag-beenden-been/260917-eta-SUMMARY.md` when done
</output>