Files

27 KiB
Raw Permalink Blame History

phase, plan, type, wave, depends_on, files_modified, files_deleted, autonomous, requirements, user_setup, estimate, must_haves
phase plan type wave depends_on files_modified files_deleted autonomous requirements user_setup estimate must_haves
18-desktop-client-fertigstellen 04 execute 2
18-01
apps/desktop/src-tauri/src/lib.rs
apps/desktop/src-tauri/Cargo.toml
apps/desktop/src-tauri/Cargo.lock
apps/desktop/src-tauri/capabilities/default.json
apps/desktop/src-tauri/tauri.conf.json
apps/desktop/src/setup.html
apps/desktop/src-tauri/icons/icon.png
apps/desktop/src-tauri/icons/icon.ico
apps/desktop/src-tauri/icons/128x128.png
apps/desktop/src-tauri/icons/128x128@2x.png
apps/desktop/src-tauri/icons/32x32.png
apps/desktop/src-tauri/apps/desktop/src-tauri/icons/128x128.png
apps/desktop/src-tauri/apps/desktop/src-tauri/icons/32x32.png
apps/desktop/src-tauri/apps/desktop/src-tauri/icons/icon.ico
apps/desktop/src-tauri/apps/desktop/src-tauri/icons/icon.png
true
DESK-01
DESK-02
DESK-05
tokens raw_tokens tasks confidence
90000 90000 2 low
truths artifacts key_links
Der Client vergleicht beim Start seine Version mit `{server}/api-proxy/desktop/latest`; weicht sie ab, zeigt er die Benachrichtigung 'Neue Version X.Y.Z verfügbar' und schaltet den Tray-Eintrag 'Update herunterladen' frei, der `{server}/settings/general/desktop` im Systembrowser öffnet (D-11, D-13).
Die Erststart-Seite fragt die Server-Adresse ab, prueft sie ueber `/api-proxy/health/version` (Rust-Kommando, kein CORS), speichert sie und laedt die Tessera-Anmeldung; Texte in Sie-Form, Tessera-Farben und -Logo (D-02, D-13).
Das Tray-Menue traegt 'Öffnen', 'Update herunterladen', den Haken 'Mit Windows starten' (auf Linux 'Beim Anmelden starten') und 'Beenden' — mit echten Umlauten; Schliessen-ins-Tray, Fensterzustand und Autostart-Plugin bleiben wie in Phase 6 (D-13, D-14).
Der Client traegt das Tessera-Zeichen als App- und Fenster-Icon (kein flaches gelbes Quadrat), und `cargo check`, `cargo clippy` sowie ein lokaler AppImage-Bau laufen durch (D-16).
path provides contains
apps/desktop/src-tauri/src/lib.rs Kommandos check_server und save_server_url, Versionspruefung gegen /desktop/latest, Tray-Eintraege update und autostart, Opener check_server
path provides contains
apps/desktop/src-tauri/capabilities/default.json opener:allow-open-url mit http/https-Scope opener:allow-open-url
path provides contains
apps/desktop/src/setup.html Erststart-Seite ohne Bundler-Import, ueber window.__TAURI__.core.invoke __TAURI__
path provides
apps/desktop/src-tauri/icons/icon.ico Mehrgroessen-ICO (16 bis 256) aus dem Tessera-Zeichen
from to via pattern
apps/desktop/src-tauri/src/lib.rs apps/api/src/desktop/desktop.controller.ts GET {server}/api-proxy/desktop/latest — Feld version api-proxy/desktop/latest
from to via pattern
apps/desktop/src/setup.html apps/desktop/src-tauri/src/lib.rs window.__TAURI__.core.invoke('check_server' | 'save_server_url') invoke('check_server'
from to via pattern
apps/desktop/src-tauri/src/lib.rs (Tray 'update') apps/web/src/app/(portal)/settings/general/desktop/page.tsx opener().open_url(`{server}/settings/general/desktop`) settings/general/desktop
Der Tauri-Client aus Phase 6 wird zum fertigen Produkt: Versionspruefung gegen `/desktop/latest` mit Update-Hinweis und Download-Link im Tray, Autostart-Haken im Tray, Umlaute in allen Tray-Texten, eine Erststart-Seite in Sie-Form mit Tessera-Gestalt, die die Adresse wirklich prueft, und ein echtes App-Icon. Der Windows-Bau wird in 18-05 in der Pipeline bewiesen; hier werden `cargo check`, `cargo clippy` und ein lokaler AppImage-Bau als Beweis vor dem Push verlangt (D-16).

Purpose: D-11, D-13 und D-14 aus 18-CONTEXT.md sowie Erfolgskriterium 3. Output: Geaenderte lib.rs, neues Plugin tauri-plugin-opener, erweiterte Capabilities, ueberarbeitete setup.html, Icon-Satz, lokal gebautes AppImage.

Zwei Befunde aus der Planung, die dieser Plan behebt:

  1. setup.html importiert das Store-Plugin als nacktes ES-Modul; ohne Bundler und ohne Importmap scheitert dieser Import im gebauten Client mit "Failed to resolve module specifier", der Knopf "Verbinden" tut dann nichts. Die Seite spricht kuenftig ausschliesslich ueber window.__TAURI__.core.invoke mit zwei Rust-Kommandos (withGlobalTauri ist bereits aktiv).
  2. Die API ist vom Client nur ueber den Web-Ursprung erreichbar (Next.js-Rewrite /api-proxy/*, siehe 18-01). Die bisherige Pruefung gegen {server}/health/version lief im Betrieb ins Leere; alle Aufrufe gehen jetzt ueber {server}/api-proxy/....

Discretion (Icon-Pruefung, Tray-Reihenfolge): Die heutigen Icons sind flache gelbe Quadrate (32x32, 105 Bytes). Sie werden aus dem Web-Zeichen apps/web/src/app/icon.svg neu erzeugt. Tray-Reihenfolge: Öffnen · Update herunterladen · — · Autostart-Haken · — · Beenden.

Artifacts this phase produces

Dieser Plan: apps/desktop/src-tauri/src/lib.rs (Funktionen api_url, check_server, save_server_url, Struktur DesktopLatest, Tray-IDs open/update/autostart/quit), Cargo.toml (+tauri-plugin-opener), Cargo.lock, capabilities/default.json (opener:allow-open-url), tauri.conf.json (Icon-Liste, CSP ohne Fremdhost), apps/desktop/src/setup.html, icons/icon.png (512), icons/128x128.png, icons/128x128@2x.png, icons/32x32.png, icons/icon.ico (16-256). Entfernt: das versehentlich verschachtelte Verzeichnis apps/desktop/src-tauri/apps/. Gesamtliste der Phase: siehe 18-01-PLAN.md.

<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/18-desktop-client-fertigstellen/18-CONTEXT.md @.planning/phases/18-desktop-client-fertigstellen/18-RESEARCH.md @.planning/phases/06-desktop-client-ci-cd/06-02-SUMMARY.md

@apps/desktop/src-tauri/src/lib.rs @apps/desktop/src-tauri/Cargo.toml @apps/desktop/src-tauri/capabilities/default.json @apps/desktop/src-tauri/tauri.conf.json @apps/desktop/src/setup.html @apps/web/src/app/icon.svg

Task 1: lib.rs — Kommandos fuer die Erststart-Seite, Versionspruefung gegen /desktop/latest, Tray mit Update und Autostart Rust/Cargo 1.96 mit Clippy ist installiert (`cargo clippy --version` antwortet), `cargo check` in `apps/desktop/src-tauri` ist am Stand von 18-01 gruen, und die Basislinie `1.1.0` aus 18-01 Task 2 ist eingecheckt. Plugin-Einbindung und Tray-Aufbau sind lokal in einer Datei; ein Rueckbau ist ein Commit. apps/desktop/src-tauri/src/lib.rs, apps/desktop/src-tauri/Cargo.toml, apps/desktop/src-tauri/Cargo.lock, apps/desktop/src-tauri/capabilities/default.json apps/desktop/src-tauri/src/lib.rs (gesamt, 121 Zeilen), apps/desktop/src-tauri/Cargo.toml, apps/desktop/src-tauri/capabilities/default.json, .planning/phases/18-desktop-client-fertigstellen/18-RESEARCH.md (Code Examples 4 und 5, "Package Legitimacy Audit"), .planning/phases/18-desktop-client-fertigstellen/18-PATTERNS.md (Abschnitte lib.rs, capabilities, Cargo.toml) - `check_server(url)` (async Tauri-Kommando) prueft Schema http/https, ruft `{url}/api-proxy/health/version` mit 8 Sekunden Zeitlimit ab und liefert `Ok(version)`; jeder Fehler liefert `Err({deutsche Meldung in Sie-Form})`. - `save_server_url(url)` normalisiert die Adresse, schreibt `server_url` in `config.json` des Store-Plugins, speichert den Store und navigiert das Fenster `main` auf die Adresse. - Beim Start mit gespeicherter Adresse laeuft die Versionspruefung gegen `{server}/api-proxy/desktop/latest`; bei `version != CARGO_PKG_VERSION` erscheint die Benachrichtigung (Titel "Tessera-Update", Text "Neue Version X.Y.Z verfügbar – Download über das Symbol im Infobereich."), und der Tray-Eintrag `update` wird aktiviert und in "Version X.Y.Z herunterladen" umbenannt. - Tray-Eintrag `update` oeffnet `{server}/settings/general/desktop` im Systembrowser ueber `tauri-plugin-opener`. - Tray-Haken `autostart` spiegelt beim Start `autolaunch().is_enabled()`; ein Klick schaltet um und setzt den Haken auf den neuen Zustand. - Tray-Texte: "Öffnen", "Update herunterladen", "Mit Windows starten" (unter `cfg!(target_os = "windows")`, sonst "Beim Anmelden starten"), "Beenden". **Abhaengigkeit.** Im Verzeichnis `apps/desktop/src-tauri` `cargo add tauri-plugin-opener@2` ausfuehren (Legitimitaetspruefung in RESEARCH: `OK`, offizielles Plugin aus `tauri-apps/plugins-workspace`; gleiche unpinnte Major-Schreibweise wie die anderen `tauri-plugin-*`-Zeilen). `Cargo.lock` wird dabei aktualisiert und mit committet.

Capabilities (capabilities/default.json). An das permissions-Array das Objekt { "identifier": "opener:allow-open-url", "allow": [ { "url": "https://*" }, { "url": "http://*" } ] } anhaengen (http://* wegen D-02: interne Server ohne TLS sind erlaubt, gleiche Begruendung wie die HTTP-Warnung der Erststart-Seite). Die Autostart-Rechte sind bereits vorhanden.

lib.rs — Imports und Plugins. Zusaetzlich use tauri_plugin_opener::OpenerExt;, use tauri_plugin_autostart::ManagerExt; (neben MacosLauncher), tauri::menu::CheckMenuItemBuilder, tauri::AppHandle, std::time::Duration. Plugin .plugin(tauri_plugin_opener::init()) registrieren und .invoke_handler(tauri::generate_handler![check_server, save_server_url]) vor .setup(...) einhaengen.

Hilfsfunktion fn api_url(server: &str, path: &str) -> String: liefert format!("{}/api-proxy{}", server.trim_end_matches('/'), path) — die einzige Stelle, an der der Rewrite-Praefix steht; Kommentar erklaert, warum (Next.js-Rewrite, API nicht unter dem Web-Hostnamen).

Kommando check_server (#[tauri::command] async fn check_server(url: String) -> Result<String, String>): tauri::Url::parse (Fehler: "Diese Adresse ist ungültig."), Schema http/https (sonst "Es sind nur Adressen mit http oder https erlaubt."), reqwest::Client::builder().timeout(Duration::from_secs(8)).build(), GET api_url(&url, "/health/version"); Netzfehler -> "Unter dieser Adresse antwortet kein Tessera-Server."; Nicht-2xx -> "Der Server antwortete mit Status {code}."; JSON in die bestehende Struktur VersionResponse (Feld version) -> Ok(version). Meldungen sind Sie-Form-tauglich (keine Anrede), Umlaute als UTF-8.

Kommando save_server_url (#[tauri::command] fn save_server_url(app: AppHandle, url: String) -> Result<(), String>): Adresse parsen und als String normalisieren (Url::as_str), Store config.json ueber app.store(...), store.set("server_url", serde_json::json!(normalized)), store.save() (Fehler als String), danach get_webview_window("main") und navigate(parsed_url).

Versionspruefung umbauen. Struktur DesktopLatest { version: String } (serde::Deserialize). Im bestehenden async_runtime::spawn-Block die Adresse durch api_url(&server_url, "/desktop/latest") ersetzen, Antwort als DesktopLatest lesen; bei Abweichung Benachrichtigung mit Titel "Tessera-Update" und Text "Neue Version {v} verfügbar – Download über das Symbol im Infobereich." und am geklonten Handle des Tray-Eintrags update set_text(format!("Version {v} herunterladen")) und set_enabled(true) aufrufen (Rueckgaben mit let _ = ignorieren, wie bisher).

Tray-Menue. Eintraege in dieser Reihenfolge: open "Öffnen"; update "Update herunterladen" mit .enabled(false) beim Bau (wird erst nach der Pruefung freigeschaltet); Trenner; autostart als CheckMenuItemBuilder::with_id("autostart", label) mit .checked(app.autolaunch().is_enabled().unwrap_or(false)), Label if cfg!(target_os = "windows") { "Mit Windows starten" } else { "Beim Anmelden starten" }; Trenner; quit "Beenden". Fuer on_menu_event vorher let server_for_menu = url_for_check.clone(); und let autostart_for_menu = autostart.clone(); anlegen (die Handles sind Clone + Send + Sync). Neue match-Arme: "update" -> wenn eine Adresse gespeichert ist, app.opener().open_url(format!("{}/settings/general/desktop", server.trim_end_matches('/')), None::<&str>); "autostart" -> let mgr = app.autolaunch(); let on = mgr.is_enabled().unwrap_or(false); dann mgr.disable() bzw. mgr.enable(), bei Erfolg set_checked(!on), bei Fehler set_checked(on) (Haken bleibt bei der Wahrheit). Die bestehenden Arme open/quit, on_tray_icon_event, on_window_event (Schliessen-ins-Tray) und RunEvent::ExitRequested bleiben unveraendert (D-14). Bestehender Kommentar "Tray menu" um zwei Saetze zu den neuen Eintraegen ergaenzen.

Nach dem Umbau cargo check und cargo clippy ausfuehren; Clippy-Warnungen in den geaenderten Zeilen beheben (bestehende Warnungen andernorts nur beheben, wenn trivial). <acceptance_criteria> - grep -c '^tauri-plugin-opener = "2"' apps/desktop/src-tauri/Cargo.toml ergibt 1; grep -c 'name = "tauri-plugin-opener"' apps/desktop/src-tauri/Cargo.lock ergibt 1. - grep -c '"opener:allow-open-url"' apps/desktop/src-tauri/capabilities/default.json ergibt 1. - grep -v '^\s*//' apps/desktop/src-tauri/src/lib.rs | grep -c 'fn check_server' ergibt 1; ebenso fn save_server_url 1, fn api_url 1, api-proxy mindestens 1, tauri_plugin_opener::init() 1, generate_handler!\[check_server, save_server_url\] 1. - grep -c '"Öffnen"' apps/desktop/src-tauri/src/lib.rs ergibt 1; grep -c '"Mit Windows starten"' apps/desktop/src-tauri/src/lib.rs ergibt 1; grep -c 'CheckMenuItemBuilder::with_id("autostart"' apps/desktop/src-tauri/src/lib.rs ergibt 1; grep -c 'settings/general/desktop' apps/desktop/src-tauri/src/lib.rs ergibt 1. - grep -c '/desktop/latest' apps/desktop/src-tauri/src/lib.rs ergibt 1. - cargo check und cargo clippy in apps/desktop/src-tauri enden mit Exit 0. </acceptance_criteria> cd /home/vicolab/projects/tessera-ctl/apps/desktop/src-tauri && cargo check 2>&1 | tail -1 | grep -q Finished && cargo clippy 2>&1 | tail -1 | grep -q Finished && echo RUST-OK <fails_when>cargo check oder cargo clippy endet nicht mit einer Finished-Zeile (Kompilier- oder Clippy-Fehler) — RUST-OK fehlt.</fails_when> cd /home/vicolab/projects/tessera-ctl && grep -v '^\s*//' apps/desktop/src-tauri/src/lib.rs | grep -q 'fn check_server' && grep -q 'fn save_server_url' apps/desktop/src-tauri/src/lib.rs && grep -q 'api-proxy' apps/desktop/src-tauri/src/lib.rs && grep -q '"Öffnen"' apps/desktop/src-tauri/src/lib.rs && grep -q 'CheckMenuItemBuilder::with_id("autostart"' apps/desktop/src-tauri/src/lib.rs && grep -q 'settings/general/desktop' apps/desktop/src-tauri/src/lib.rs && grep -q '"opener:allow-open-url"' apps/desktop/src-tauri/capabilities/default.json && grep -q '^tauri-plugin-opener = "2"' apps/desktop/src-tauri/Cargo.toml && echo WIRING-OK <fails_when>Eines der Kennzeichen (Kommandos, Rewrite-Praefix, Umlaut-Label, Autostart-Haken, Update-Link, Capability, Abhaengigkeit) fehlt — WIRING-OK fehlt.</fails_when> lib.rs kompiliert mit Opener-Plugin, beiden Kommandos, Versionspruefung gegen /api-proxy/desktop/latest, Tray mit Update-Eintrag und Autostart-Haken und Umlaut-Texten; Capabilities und Cargo-Dateien sind nachgezogen; cargo check/cargo clippy gruen.

Task 2: Erststart-Seite in Tessera-Gestalt und Sie-Form, echtes App-Icon, lokaler AppImage-Beweis ImageMagick 7 (`magick`) ist auf dem Entwicklungsrechner vorhanden (am 2026-09-16 geprueft: 7.1.1, mit SVG- und ICO-Unterstuetzung); die Tauri-Linux-Abhaengigkeiten aus 18-01 Task 1 sind installiert. apps/desktop/src/setup.html, apps/desktop/src-tauri/tauri.conf.json, apps/desktop/src-tauri/icons/icon.png, apps/desktop/src-tauri/icons/icon.ico, apps/desktop/src-tauri/icons/128x128.png, apps/desktop/src-tauri/icons/128x128@2x.png, apps/desktop/src-tauri/icons/32x32.png apps/desktop/src/setup.html (gesamt, 254 Zeilen), apps/desktop/src-tauri/tauri.conf.json, apps/web/src/app/icon.svg (Tessera-Zeichen, 72x72), apps/web/src/components/brand/brand.ts (BRAND_YELLOW #ffed00, BRAND_OLIVE #9c9440, BRAND_PLATE #1a1a1a), .gitea/scripts/desktop-collect.sh (aus 18-01) - Die Seite laedt keinen Modulcode von aussen und importiert kein npm-Paket; sie nutzt `window.__TAURI__.core.invoke`. - Klick auf "Verbinden" (oder Enter): Adresse pruefen (leer, ungueltig, falsches Schema -> Fehlertext in Sie-Form), dann `invoke('check_server', { url })`; bei Fehler erscheint die Meldung des Kommandos, der Knopf ist wieder bedienbar; bei Erfolg erscheint kurz "Tessera {version} gefunden – Verbindung wird hergestellt …" und `invoke('save_server_url', { url })` fuehrt zur Tessera-Anmeldung. - Bei http ausserhalb von localhost bleibt die Warnung (Sie-Form) sichtbar, die Verbindung ist erlaubt (D-02). - Die Seite zeigt das Tessera-Zeichen (inline-SVG aus `icon.svg`) und den Schriftzug "Tessera" in Markengelb auf dunklem Grund; keine vorbelegte Server-Adresse, nur der Platzhalter `https://tessera.example.com`. - Das App-Icon ist das Tessera-Zeichen in 512x512 (PNG) und als ICO mit den Groessen 16, 32, 48, 64, 128, 256. - `pnpm --filter @tessera/desktop exec tauri build --bundles appimage` laeuft lokal durch und `desktop-collect.sh` sammelt `Tessera-1.1.0.AppImage` ein. **`setup.html` — Skript.** Den `<script type="module">`-Block umschreiben: kein `import`-Statement mehr; am Anfang `const { invoke } = window.__TAURI__.core;`. `validateUrl` behalten (Logik unveraendert), Meldungen ersetzen: leer -> "Bitte geben Sie die Adresse Ihres Tessera-Servers ein."; ungueltig -> "Diese Adresse ist ungültig. Bitte geben Sie eine vollständige Adresse ein, z. B. https://tessera.example.com."; Schema -> "Es sind nur Adressen mit http oder https erlaubt."; Warnung -> "Hinweis: Diese Verbindung ist unverschlüsselt (http). Für den Produktivbetrieb empfehlen wir https.". `connect()`: nach der Pruefung Knopf sperren, Text "Prüfe Verbindung …", `const version = await invoke('check_server', { url: normalizedUrl })` im `try`; im `catch` `showError(String(err))` und Knopf freigeben ("Verbinden"); bei Erfolg `showInfo('Tessera ' + version + ' gefunden – Verbindung wird hergestellt …')` (neue Hilfsfunktion und ein `

` im gleichen Stil wie die Warnung, gruenliche Farbe) und `await invoke('save_server_url', { url: normalizedUrl })`; schlaegt das Speichern fehl: "Die Adresse konnte nicht gespeichert werden: …". Enter-Taste und Eingabe-Reset bleiben.

setup.html — Markup und Gestalt (Sie-Form, Tessera-Farben). Ueber der Ueberschrift das Tessera-Zeichen als inline-SVG (Inhalt von apps/web/src/app/icon.svg, Breite 56px), <h1> "Tessera" in #ffed00, Untertitel "Desktop-App einrichten", Label "Adresse Ihres Tessera-Servers", darunter ein Hilfstext <p class="hint"> "Das ist die Adresse, unter der Sie Tessera auch im Browser öffnen." Das value-Attribut des Eingabefelds entfernen (keine vorbelegte Adresse — ein Paket fuer alle Umgebungen, D-02), Platzhalter https://tessera.example.com bleibt. Knopf "Verbinden" in Markengelb mit dunkler Schrift (#1a1a1a), Karte dunkel (oklch(0.22 0.01 260)), Rahmenfarbe in Olive (#9c9440) fuer Fokus. Alle Texte mit echten Umlauten (<meta charset="UTF-8"> steht bereits). <title> "Tessera – Desktop-App einrichten".

CSP (tauri.conf.json). In app.security.csp bei script-src den Fremdhost-Eintrag entfernen, sodass dort nur noch 'self' 'unsafe-inline' 'unsafe-eval' steht (die Seite laedt nichts mehr von aussen; T-18-11). connect-src * bleibt (D-02).

Icons. Aus apps/web/src/app/icon.svg erzeugen (im Verzeichnis apps/desktop/src-tauri/icons): magick -background none -density 512 ../../../web/src/app/icon.svg -resize 512x512 icon.png; daraus 128x128.png (128), 128x128@2x.png (256), 32x32.png (32) per -resize; icon.ico mit magick icon.png -define icon:auto-resize=256,128,64,48,32,16 icon.ico. In tauri.conf.json bundle.icon auf ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.png", "icons/icon.ico"] setzen. Das versehentlich verschachtelte, versionierte Verzeichnis apps/desktop/src-tauri/apps/ mit git rm -r entfernen (Rest aus Phase 6).

Lokaler Beweis (D-16). rm -rf apps/desktop/src-tauri/target/release/bundle, dann pnpm --filter @tessera/desktop exec tauri build --bundles appimage (Version bleibt 1.1.0 aus der Basislinie), danach sh .gitea/scripts/desktop-collect.sh --require linux. Im SUMMARY die Baudauer und die Groesse des AppImage festhalten. Optional, wenn eine grafische Sitzung vorhanden ist: das AppImage starten, Erststart-Seite ansehen, http://localhost:3000 eingeben, Anmeldung sehen; Ergebnis im SUMMARY notieren (kein Pflichtschritt — die Windows-Probe macht der Nutzer am Phasenende). <acceptance_criteria> - grep -c "window.__TAURI__.core" apps/desktop/src/setup.html ergibt 1; grep -c "invoke('check_server'" apps/desktop/src/setup.html ergibt 1; grep -c "invoke('save_server_url'" apps/desktop/src/setup.html ergibt 1. - grep -c '^\s*import ' apps/desktop/src/setup.html ergibt 0 (kein Modul-Import mehr). - grep -c 'unpkg.com' apps/desktop/src-tauri/tauri.conf.json ergibt 0. - grep -c 'value="http://localhost:3000"' apps/desktop/src/setup.html ergibt 0; grep -c 'Adresse Ihres Tessera-Servers' apps/desktop/src/setup.html ergibt mindestens 1. - magick identify -format '%wx%h\n' apps/desktop/src-tauri/icons/icon.png ergibt 512x512; magick identify apps/desktop/src-tauri/icons/icon.ico | wc -l ergibt 6. - test ! -e apps/desktop/src-tauri/apps endet mit 0 (verschachteltes Verzeichnis per git rm -r entfernt). - jq -r '.bundle.icon | length' apps/desktop/src-tauri/tauri.conf.json ergibt 5. - desktop-dist/manifest.json traegt Tessera-1.1.0.AppImage aus dem frischen Bau (Zeitstempel des AppImage neuer als der von Task 1 geaenderten lib.rs). </acceptance_criteria>

cd /home/vicolab/projects/tessera-ctl && grep -q "window.__TAURI__.core" apps/desktop/src/setup.html && grep -q "invoke('check_server'" apps/desktop/src/setup.html && grep -q "invoke('save_server_url'" apps/desktop/src/setup.html && test "$(grep -c '^\s*import ' apps/desktop/src/setup.html)" = "0" && test "$(grep -c 'unpkg.com' apps/desktop/src-tauri/tauri.conf.json)" = "0" && test "$(magick identify -format '%wx%h' apps/desktop/src-tauri/icons/icon.png)" = "512x512" && test "$(magick identify apps/desktop/src-tauri/icons/icon.ico | wc -l)" = "6" && test ! -e apps/desktop/src-tauri/apps && echo SETUP-OK Ein Kennzeichen fehlt, ein Modul-Import ist noch da, der Fremdhost steht noch in der CSP, ein Icon hat die falsche Groesse/Anzahl, oder das verschachtelte Verzeichnis ist noch versioniert — `SETUP-OK` fehlt. cd /home/vicolab/projects/tessera-ctl && test -n "$(find apps/desktop/src-tauri/target/release/bundle/appimage -name '*.AppImage' -newer apps/desktop/src-tauri/src/lib.rs)" && sh .gitea/scripts/desktop-collect.sh --require linux && test "$(jq -r .files.linux.name desktop-dist/manifest.json)" = "Tessera-1.1.0.AppImage" && echo APPIMAGE-OK Kein AppImage, das neuer als die geaenderte `lib.rs` ist (der lokale Bau lief nicht oder scheiterte), das Sammel-Skript bricht ab, oder das Manifest nennt nicht `Tessera-1.1.0.AppImage` — `APPIMAGE-OK` fehlt. Die Erststart-Seite spricht nur noch ueber `invoke`, prueft die Adresse serverseitig, ist in Sie-Form und Tessera-Gestalt; die CSP laedt nichts von aussen; der Icon-Satz zeigt das Tessera-Zeichen; ein frischer lokaler AppImage-Bau mit dem neuen Client liegt eingesammelt in `desktop-dist/`.

<threat_model>

Trust Boundaries

Boundary Description
Erststart-Seite -> Rust-Kommandos (invoke) Die vom Anwender eingegebene Adresse wird an check_server/save_server_url uebergeben.
Client -> Server (/api-proxy/health/version, /api-proxy/desktop/latest) Ausgehende HTTP-Aufrufe an die gespeicherte Adresse.
Tray -> Systembrowser (opener) Der Client oeffnet eine Adresse ausserhalb der App.
WebView -> entfernte Web-App Nach dem Erststart laeuft die Tessera-Web-App im WebView (unveraendert seit Phase 6).

STRIDE Threat Register

Threat ID Category Component Severity Disposition Mitigation Plan
T-18-10 Spoofing / Tampering check_server/save_server_url (beliebige Adresse) low mitigate Nur http/https, Adresse wird geparst und normalisiert; der Aufruf geht nur an die vom Anwender selbst eingegebene Adresse, ausschliesslich auf dessen Rechner (kein Server-seitiges SSRF). Zeitlimit 8 s.
T-18-11 Tampering CSP der lokalen Seite (Fremdhost in script-src) low mitigate Fremdhost entfernt; die Seite laedt keinen externen Code mehr.
T-18-12 Elevation of Privilege Tray update (Opener) low mitigate Adresse wird aus der gespeicherten server_url gebaut, nie aus Serverdaten; Capability auf http://*/https://* beschraenkt (kein file:/Schema-Missbrauch).
T-18-13 Spoofing Update-Hinweis aus /desktop/latest (falsche Version vorgetaeuscht) low accept Der Hinweis fuehrt nur auf die Tessera-Seite; kein Auto-Update, kein Download ohne Nutzeraktion (D-03).
T-18-14 Information Disclosure Unsignierte Binaries / SmartScreen low accept Keine Code-Signierung in dieser Phase (D-09); Erklaerung im Anwenderhandbuch (18-06).
T-18-SC Tampering cargo add tauri-plugin-opener@2 low mitigate Legitimitaetspruefung in RESEARCH: OK (tauri-apps/plugins-workspace, 374k Downloads/Woche); Lockfile committet; kein [ASSUMED]/[SUS]-Paket, daher keine Sperr-Freigabe noetig.
</threat_model>
1. `cargo check` und `cargo clippy` in `apps/desktop/src-tauri` — gruen. 2. Kennzeichen-Greps fuer Kommandos, Rewrite-Praefix, Umlaute, Capability, Abhaengigkeit, Icon-Groessen — alle erfuellt. 3. Lokaler AppImage-Bau nach dem Umbau erfolgreich, `desktop-collect.sh` liefert `Tessera-1.1.0.AppImage`. 4. Der Windows-Bau desselben Stands wird in 18-05 in der Pipeline bewiesen; die Bedienprobe (Erststart, Tray, Anmeldung) macht der Nutzer am Phasenende (18-06).

<success_criteria>

  • Erststart-Seite prueft die Adresse wirklich, speichert sie und fuehrt zur Anmeldung; Sie-Form, Tessera-Gestalt, kein Fremdcode.
  • Tray: Öffnen · Update herunterladen · Autostart-Haken · Beenden, mit Umlauten; Update-Eintrag oeffnet die Download-Seite im Browser.
  • Versionspruefung gegen /api-proxy/desktop/latest mit Benachrichtigung.
  • Echtes App-Icon; cargo check/clippy und lokaler AppImage-Bau gruen. </success_criteria>
Create `.planning/phases/18-desktop-client-fertigstellen/18-04-SUMMARY.md` when done. Im SUMMARY festhalten: Baudauer und Groesse des AppImage, ob eine grafische Probe moeglich war, und alle Clippy-Warnungen, die bewusst stehen blieben.