docs(quick-260917-eta): Plan — Desktop-Client Tray: „Beenden“ beendet (ExitRequested nur bei code None verhindern), „Öffnen“/Linksklick per unminimize zurückholen, CHANGELOG
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+150
@@ -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."
|
||||||
|
---
|
||||||
|
|
||||||
|
<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`).
|
||||||
|
|
||||||
|
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`
|
||||||
|
</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>
|
||||||
|
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`
|
||||||
|
</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>
|
||||||
Reference in New Issue
Block a user