--- phase: quick-260917-jn2 plan: 01 subsystem: desktop-client tags: [tauri, rust, tray, nextjs, i18n, desktop] # Dependency graph requires: - phase: 18-desktop-client-fertigstellen provides: Tauri-Desktop-Client mit Tray-Menue (open/update/autostart/quit), Setup-Seite (Erststart), Web-Einstellungsseite Desktop-App - phase: quick-260917-eta provides: Run-Handler-Muster (code: None vs. code: Some), unminimize() vor show() in "open" und Linksklick — unangetastet uebernommen provides: - "Tray zeigt den verbundenen Server an drei Stellen: Tooltip 'Tessera – {host}', gesperrte erste Menuezeile 'Verbunden mit {host}', Web-Einstellungsseite 'Verbunden mit: {origin}'" - "Neuer Tray-Eintrag 'Server-Adresse ändern…' navigiert zur gebuendelten Setup-Seite im Aenderungsmodus (vorbelegtes Feld, 'Aktuell verbunden mit: …', Knopf 'Abbrechen')" - "Nach einem Serverwechsel aktualisieren sich Tooltip, Menuezeile und Update-Ziel ohne Neustart; die Versionspruefung laeuft neu gegen den neuen Server" - "Vier reine, getestete Helfer server_host/tray_labels/setup_page_url/parse_server_url plus stored_server_url als einzige Store-Lesestelle" affects: [desktop-client, tray-verhalten, updater] # Actuals (#2632) actuals: tokens: 8317 tasks: 3 commits: 3 plan_head_before: b023d6f72655706167d72430337470ad3722144f # Tech tracking tech-stack: added: [] patterns: - "app.manage(TrayItems { connected, update }) haelt die Menue-Handles im State — einzige Stelle, die Tooltip/Menuezeile (apply_server) und den Update-Eintrag (spawn_version_check) ohne Neustart setzt" - "stored_server_url(app) als einzige Store-Lesestelle (Start, get_server_url, open_server, Tray-Klick 'update') statt mehrfacher Store-Zugriffe" - "App-Commands ohne Capability-Eintrag: lokale Herkunft (tauri://localhost / http://tauri.localhost) erlaubt sie implizit, Remote-Ursprung verweigert Tauri sie mangels remote-Block (T-JN2-01)" key-files: created: [] modified: - apps/desktop/src-tauri/src/lib.rs - apps/desktop/src/setup.html - apps/web/src/components/settings/desktop-app-settings.tsx - apps/web/src/components/settings/desktop-app-settings.test.tsx - apps/web/src/messages/de.json - apps/web/src/messages/en.json - docs/anleitung-anwender.md - CHANGELOG.md key-decisions: - "Pruefpunkt 5 des Task-1-Gates (grep nach einzeiligem generate_handler!\\[...\\]) liess sich nicht woertlich erfuellen: rustfmt bricht die vier Handler-Namen (109 Zeichen) legitim auf mehrere Zeilen um. cargo fmt --check MUSS gruen bleiben (Bestand ist rustfmt-konform) — darum durch einen semantisch gleichwertigen mehrzeiligen Nachweis ersetzt (Rule 3, Detail unten unter Abweichungen)." - "Startnavigation zur gespeicherten Adresse bleibt VOR dem Tray-Aufbau (wie im Bestand); apply_server()/spawn_version_check() laufen NACH dem Tray-Aufbau, weil sie app.state::() brauchen, das erst beim Tray-Aufbau gemanagt wird." - "spawn_version_check() setzt den Update-Eintrag zuerst synchron auf UPDATE_ITEM_DEFAULT zurueck, bevor der async-Block startet — verhindert, dass nach einem Wechsel ein veralteter Update-Hinweis des alten Servers sichtbar bleibt." patterns-established: - "Tray-Menue-Struktur: connected (gesperrt) · — · open · change_server · update · — · autostart · — · quit; TrayIconBuilder::with_id(\"main\") + app.tray_by_id(\"main\") als Paar fuer spaetere Auffrischung" requirements-completed: [QUICK-260917-JN2] coverage: - id: D1 description: "Rust: vier reine Helfer (server_host, tray_labels, setup_page_url, parse_server_url) plus stored_server_url, TrayItems-State, apply_server, spawn_version_check, Commands get_server_url/open_server; capabilities/default.json unveraendert" requirement: "QUICK-260917-JN2" verification: - kind: unit ref: "apps/desktop/src-tauri/src/lib.rs mod tests — 18 Tests (13 neu + 5 Bestand), cargo test --lib" status: pass - kind: other ref: "cargo fmt --check, cargo check, cargo clippy — je 0 Warnungen/Fehler" status: pass human_judgment: false - id: D2 description: "setup.html im Aenderungsmodus: get_server_url beim Laden, Feld vorbelegt, 'Aktuell verbunden mit: …', Knopf 'Abbrechen' → open_server; ohne gespeicherte Adresse Erststart-Verhalten unveraendert" requirement: "QUICK-260917-JN2" verification: - kind: other ref: "Grep-Gate Task 1 (invoke('get_server_url'), invoke('open_server'), #cancel-btn, #current-server, brand-mark-SVG unveraendert)" status: pass human_judgment: true rationale: "Reale Bedienprobe (Tooltip, Menue, Klick auf 'Server-Adresse ändern…', vorbelegte Seite, 'Abbrechen', Wechsel aktualisiert Tooltip/Menue/Update-Ziel ohne Neustart) erfordert die Windows-Test-VM mit dem CI-Paket — macht laut Plan der Orchestrator im Anschluss, nicht dieser Ausfuehrungslauf." - id: D3 description: "Web: DesktopAppSettings zeigt im Desktop-Client 'Verbunden mit: {origin}' plus Aenderungshinweis; im Browser fehlt der Block; beide Sprachen" requirement: "QUICK-260917-JN2" verification: - kind: unit ref: "apps/web/src/components/settings/desktop-app-settings.test.tsx — Test 4 (Cookie) und Test 5 (ohne Cookie), 5/5 Tests gruen" status: pass - kind: unit ref: "pnpm --filter @tessera/web exec vitest run — 431/431 gruen; pnpm --filter @tessera/web type-check — 0 Fehler" status: pass human_judgment: false - id: D4 description: "Handbuch: neuer Unterabschnitt 'Server-Adresse ändern', erweiterte Tray-Liste, Erststart-Verweis; CHANGELOG zwei Desktop-App-Stichpunkte unter Unveröffentlicht/Neu" requirement: "QUICK-260917-JN2" verification: - kind: other ref: "Grep-Gate Task 3 (Ueberschrift, Tray-Liste, 'Aktuell verbunden mit', CHANGELOG-Zeilenzahl) + git diff CHANGELOG.md gegengeprueft (nur die 2 Zeilen neu)" status: pass - kind: unit ref: "apps/web/src/lib/changelog.test.ts — 10/10 gruen" status: pass human_judgment: false duration: ~25min (nicht exakt gestoppt) completed: 2026-09-17 status: complete --- # Quick Task 260917-jn2: Desktop-Client — aktuelle Server-Adresse sehen und ändern Summary **Tray zeigt an drei Stellen (Tooltip, gesperrte Menuezeile, Web-Einstellungsseite), mit welchem Tessera-Server der Desktop-Client verbunden ist, und ein neuer Tray-Eintrag „Server-Adresse ändern…" fuehrt zur Setup-Seite im Aenderungsmodus — ohne config.json zu loeschen und ohne Neustart der App.** ## Performance - **Duration:** ~25 min (RED/GREEN-Zyklus fuer Rust-Helfer und Web-Block, drei Verifikations-Gates) - **Completed:** 2026-09-17T14:53:26+02:00 - **Tasks:** 3/3 - **Files modified:** 8 ## Accomplishments - **Rust (`lib.rs`)**: vier reine, getestete Helfer `server_host`, `tray_labels`, `setup_page_url`, `parse_server_url` sowie `stored_server_url` als einzige Store-Lesestelle; neuer State `TrayItems { connected, update }`; `apply_server()` setzt Tooltip + Menuezeile aus derselben Quelle; `spawn_version_check()` (aus `setup` herausgezogen) laeuft nach jedem Wechsel neu und setzt den Update-Eintrag zuerst zurueck. Zwei neue Commands `get_server_url`/`open_server`, beide nur vom lokalen Ursprung aufrufbar (kein Capability-Eintrag noetig, `capabilities/default.json` unveraendert). Tray jetzt `TrayIconBuilder::with_id("main")` mit Zeilen „Verbunden mit …" (gesperrt), „Öffnen", „Server-Adresse ändern…", „Update herunterladen", Autostart, „Beenden". - **`setup.html`**: erkennt beim Laden per `get_server_url` den Aenderungsmodus (Feld vorbelegt, „Aktuell verbunden mit: …", Knopf „Abbrechen" → `open_server`); ohne gespeicherte Adresse bleibt der Erststart unveraendert. SVG-Bildmarke unangetastet. - **Web**: `DesktopAppSettings` zeigt im Desktop-Client (`useIsDesktopClient()`) einen Block „Verbunden mit: {origin}" plus Aenderungshinweis; im Browser fehlt er. Zwei neue i18n-Schluessel in beiden Sprachen. - **Doku**: neuer Handbuch-Unterabschnitt „Server-Adresse ändern", erweiterte Tray-Liste, zwei CHANGELOG-Stichpunkte unter Unveröffentlicht/Neu. ## Task Commits Each task was committed atomically: 1. **Task 1: Rust + setup.html — Verbunden-Zeile/Tooltip, „Server-Adresse ändern…", Commands, Tray-Auffrischung, Tests** - `29c132e` (feat) 2. **Task 2: Web — Block „Verbunden mit: {origin}" nur im Client, i18n, Test** - `4c79874` (feat) 3. **Task 3: Handbuch und CHANGELOG** - `4d48543` (docs) _Hinweis: kein separater `test(...)`-Commit trotz `tdd="true"` — die Orchestrator-Vorgabe fuer diesen Quick-Task lautet ausdruecklich drei Commits (einer je Task, `feat(desktop)`/`feat(web)`/`docs`). RED (Tests zuerst, Fehlschlag beobachtet) und GREEN (Implementierung, Tests gruen) liefen innerhalb jedes Tasks, aber nur ein Commit je Task wurde erzeugt — siehe „TDD Gate Compliance" unten._ ## Files Created/Modified - `apps/desktop/src-tauri/src/lib.rs` — vier reine Helfer + `mod tests` (18 Tests), `TrayItems`, `apply_server`, `spawn_version_check`, Commands `get_server_url`/`open_server`, Tray-Aufbau mit `connected`/`change_server` - `apps/desktop/src/setup.html` — Aenderungsmodus (`#current-server`, `#cancel-btn`, `#subtitle`, `init()`, `enterChangeMode()`) - `apps/web/src/components/settings/desktop-app-settings.tsx` — Block `data-testid="desktop-connected"` nur im Desktop-Client - `apps/web/src/components/settings/desktop-app-settings.test.tsx` — Test 4 (Cookie) und Test 5 (ohne Cookie) - `apps/web/src/messages/de.json`, `en.json` — `settings.desktop.connectedTo`, `settings.desktop.changeHint` - `docs/anleitung-anwender.md` — Unterabschnitt „Server-Adresse ändern", erweiterte Tray-Liste, Erststart-Verweis - `CHANGELOG.md` — zwei `Desktop-App:`-Stichpunkte unter Unveröffentlicht/Neu ## Endgueltige Namen fuer den Folgeplan (Updater, `tauri-plugin-updater`) Der naechste Task baut auf dieser Tray-Struktur auf — hier die endgueltigen Signaturen: - `struct TrayItems { connected: tauri::menu::MenuItem, update: tauri::menu::MenuItem }`, gemanagt via `app.manage(TrayItems { connected: connected.clone(), update: update.clone() })` direkt nach dem Menue-Bau, vor `TrayIconBuilder::with_id("main")...build(app)?`. - `fn apply_server(app: &AppHandle, url: Option<&str>)` — einzige Stelle, die `app.tray_by_id("main")` (Tooltip) und `app.state::().connected` (Menuezeile) setzt. - `fn spawn_version_check(app: AppHandle, server_url: String)` — nimmt `AppHandle` (nicht `&AppHandle`) und den Server-String per Wert entgegen; holt `update_item` aus `app.state::()`, setzt ihn synchron auf `UPDATE_ITEM_DEFAULT`/`enabled(false)` zurueck, dann `tauri::async_runtime::spawn(...)` wie im Bestand. Der Updater-Task kann hier andocken (z. B. den Update-Klick auf `tauri-plugin-updater` statt `opener::open_url` umstellen) — `update_item` ist bereits das MenuItem-Handle, `UPDATE_ITEM_DEFAULT` (`&str`-Konstante) der Ruecksetz-Text. - `fn stored_server_url(app: &AppHandle) -> Option` — einzige Store-Lesestelle; der Updater-Task sollte KEINE eigene Store-Lesung einfuehren, sondern diese Funktion wiederverwenden. - Tray-Menue-Reihenfolge: `connected` (gesperrt) · Trenner · `open` · `change_server` · `update` · Trenner · `autostart` · Trenner · `quit`. Ein neuer Updater-Eintrag würde vermutlich zwischen `update` und dem folgenden Trenner eingefuegt, oder `update` selbst würde umgewidmet — Entscheidung liegt beim Folgeplan. ## Decisions Made - **Pruefpunkt 5 des Task-1-Gates (Rule 3, Blocking):** Die Plan-Verifikation verlangt einen woertlichen Ein-Zeilen-Treffer `generate_handler!\[check_server, save_server_url, get_server_url, open_server\]`. Die vollstaendige Zeile ist 109 Zeichen lang, rustfmt (max_width 100, Standard) bricht sie beim Pflicht-Schritt `cargo fmt` legitim auf fuenf Zeilen um. `cargo fmt --check` MUSS gruen sein (im Bestand rustfmt-konform, keine Sonderregel wie bei 260917-eta) — das genannte Format war also nicht gleichzeitig mit einer bestandenen Formatpruefung erreichbar. Ich habe den betroffenen Pruefpunkt durch einen semantisch gleichwertigen mehrzeiligen Nachweis ersetzt (`perl -0777` Regex ueber den Macro-Block) und alle anderen 15 Pruefpunkte des Gates woertlich wie im Plan laufen lassen — alle gruen. Kein Code geaendert, nur die Pruefmethode fuer diesen einen Punkt. - **Reihenfolge im `setup`-Closure:** Startnavigation zur gespeicherten Adresse laeuft weiterhin VOR dem Tray-Aufbau (wie im Bestand), `apply_server`/`spawn_version_check` laufen NACH dem Tray-Aufbau, weil beide `app.state::()` brauchen, das erst beim Tray-Aufbau gemanagt wird (`app.manage(...)` direkt vor `TrayIconBuilder::with_id("main")`). ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 3 - Blocking, Pruefmethode] Ein-Zeilen-Grep im Task-1-Gate gegen rustfmt-Umbruch ersetzt** - **Found during:** Task 1, Verifikationslauf - **Issue:** `grep -q 'generate_handler!\[check_server, save_server_url, get_server_url, open_server\]'` verlangt eine 109-Zeichen-Zeile; `cargo fmt` (Pflichtschritt im selben Gate) bricht sie legitim um, weil sie ueber `max_width = 100` liegt. Woertlich erfuellbar waren beide Anforderungen nicht gleichzeitig. - **Fix:** Den betroffenen Pruefpunkt durch eine mehrzeilige, semantisch gleichwertige Pruefung ersetzt (`perl -0777` Regex, das denselben `generate_handler!`-Block mit denselben vier Namen in beliebiger Zeilenaufteilung matcht). Der generierte Code selbst blieb unveraendert — `cargo fmt --check` ist gruen, die vier Handler sind registriert. - **Files modified:** keine (nur die Pruefmethode fuer diesen einen Punkt, kein Code) - **Verification:** `cargo fmt --check && cargo check && cargo clippy && cargo test --lib` gruen (18 Tests); alle 15 uebrigen Pruefpunkte des Gates woertlich wie im Plan, gruen; `perl`-Ersatzpruefung fuer Punkt 5 gruen. - **Committed in:** `29c132e` (Task-1-Commit) --- **Total deviations:** 1 auto-fixed (1 blocking/Pruefmethode) **Impact on plan:** Kein Codeverhalten geaendert, nur wie ein einzelner Gate-Punkt gegen den (korrekten) rustfmt-Output geprueft wurde. Keine Abweichung von den `must_haves.truths` oder vom Threat-Register. ## TDD Gate Compliance Alle drei Tasks liefen als RED → GREEN innerhalb eines einzigen Commits je Task (Orchestrator-Vorgabe: genau drei Commits, `feat(desktop)`/`feat(web)`/`docs`): - **Task 1 (Rust, tdd="true"):** RED — 13 neue Tests in `mod tests` ergaenzt, `cargo test --lib` schlug mit 13 Compile-Fehlern fehl (fehlende Funktionen `server_host`, `tray_labels`, `setup_page_url`, `parse_server_url`), exakt auf die neuen Tests zurueckfuehrbar. GREEN — Helfer implementiert, `cargo test --lib` lief mit 18/18 gruenen Tests durch. Ein Commit (`29c132e`). - **Task 2 (Web, tdd="true"):** RED — Test 4 ("Verbunden mit: http://localhost:3000") und Test 5 ergaenzt; Testlauf zeigte Test 4 fehlschlagend auf der Zielbehauptung (Block nicht gefunden), Tests 1-3 und 5 bereits gruen. GREEN — Block implementiert, i18n-Schluessel ergaenzt; 5/5 Tests gruen, danach 431/431 Web-Tests + type-check gruen. Ein Commit (`4c79874`). - **Task 3 (kein `tdd="true"`):** Standard-Task, kein RED/GREEN-Zyklus vorgesehen. Kein separater `test(...)`-Commit vor dem jeweiligen `feat(...)`-Commit — bewusst, siehe Hinweis unter „Task Commits" oben; die formale RED/GREEN-Gate-Pruefung via `gsd_run check tdd-red-evidence` gilt fuer Plaene mit `type: tdd` in der Frontmatter, dieser Plan hat `type: execute` mit `tdd="true"` je Task. ## Issues Encountered None ueber die oben dokumentierte Abweichung hinaus. ## Nachweis durch Orchestrator (offen) Windows-VM-Bedienprobe mit dem CI-Paket steht noch aus: - **Tooltip** beim Ueberfahren des Tray-Symbols zeigt `Tessera – {host}` (bzw. `Tessera – nicht verbunden` ohne Adresse). - **Menuezeile**: erste Zeile `Verbunden mit {host}` (gesperrt, nicht anklickbar). - **Adresse ändern**: Rechtsklick → „Server-Adresse ändern…" navigiert zur Setup-Seite; Feld ist mit der aktuellen Adresse vorbelegt, Zeile „Aktuell verbunden mit: …" sichtbar, Knopf „Abbrechen" vorhanden. - **Abbrechen**: fuehrt ohne Aenderung zurueck zur laufenden Verbindung (App navigiert zur bisherigen Adresse). - **Wechsel**: neue Adresse eintragen → „Verbinden" → Tooltip, Menuezeile und Update-Ziel zeigen sofort die neue Adresse, ohne Neustart der App. - **Update-Link nach Wechsel**: die Versionspruefung laeuft neu gegen den neuen Server; ein veralteter Update-Hinweis des alten Servers darf nicht mehr sichtbar sein (Update-Eintrag ist beim Wechsel zunaechst wieder gesperrt). Alle automatisierten Verifikationen (Rust: fmt/check/clippy/test, Web: vitest/type-check, Doku-Gates) sind bereits gruen — siehe „Task Commits" und „Coverage" oben. ## Next Phase Readiness - Tray-Struktur (`TrayItems`, `apply_server`, `spawn_version_check`, `stored_server_url`) ist bereit fuer den Updater-Task (`tauri-plugin-updater`) — Signaturen siehe Abschnitt oben. - Windows-VM-Bedienprobe (Tooltip, Menue, Wechsel, Abbrechen, Update-Link) folgt durch den Orchestrator mit dem CI-Paket. --- *Quick Task: 260917-jn2* *Completed: 2026-09-17* ## Nachweis durch Orchestrator (2026-09-17, Windows-Test-VM 8233, Paket 1.2.0-beta.5a444ec) — erbracht - Tooltip „Tessera – alpha.tessera.ctl.de", Menuezeile „Verbunden mit alpha.tessera.ctl.de" (gesperrt). - „Server-Adresse ändern…" oeffnet die Setup-Seite mit „Aktuell verbunden mit: https://alpha.tessera.ctl.de/", Adresse vorbelegt; „Abbrechen" fuehrt zur Server-Seite zurueck. - Wechsel auf `http://192.168.13.11:3000`: Anmeldeseite des neuen Servers, Tooltip/Menuezeile sofort „192.168.13.11:3000", Versionspruefung lief neu — ohne Neustart. - „Beenden": Fenster zu, Tray-Symbol weg, `tasklist` ohne `tessera-desktop.exe`. - Befund (Altlast, durch 260917-kgc behoben): gegen einen aelteren Server (1.1.0) bot der Client „Version 1.1.0 herunterladen" an.