410 lines
27 KiB
Markdown
410 lines
27 KiB
Markdown
---
|
||
phase: 18-desktop-client-fertigstellen
|
||
plan: 04
|
||
type: execute
|
||
wave: 2
|
||
depends_on: ["18-01"]
|
||
files_modified:
|
||
- 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
|
||
files_deleted:
|
||
- 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
|
||
autonomous: true
|
||
requirements: [DESK-01, DESK-02, DESK-05]
|
||
user_setup: []
|
||
|
||
estimate:
|
||
tokens: 90000
|
||
raw_tokens: 90000
|
||
tasks: 2
|
||
confidence: low
|
||
|
||
must_haves:
|
||
truths:
|
||
- "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)."
|
||
artifacts:
|
||
- path: "apps/desktop/src-tauri/src/lib.rs"
|
||
provides: "Kommandos check_server und save_server_url, Versionspruefung gegen /desktop/latest, Tray-Eintraege update und autostart, Opener"
|
||
contains: "check_server"
|
||
- path: "apps/desktop/src-tauri/capabilities/default.json"
|
||
provides: "opener:allow-open-url mit http/https-Scope"
|
||
contains: "opener:allow-open-url"
|
||
- path: "apps/desktop/src/setup.html"
|
||
provides: "Erststart-Seite ohne Bundler-Import, ueber window.__TAURI__.core.invoke"
|
||
contains: "__TAURI__"
|
||
- path: "apps/desktop/src-tauri/icons/icon.ico"
|
||
provides: "Mehrgroessen-ICO (16 bis 256) aus dem Tessera-Zeichen"
|
||
key_links:
|
||
- from: "apps/desktop/src-tauri/src/lib.rs"
|
||
to: "apps/api/src/desktop/desktop.controller.ts"
|
||
via: "GET {server}/api-proxy/desktop/latest — Feld version"
|
||
pattern: "api-proxy/desktop/latest"
|
||
- from: "apps/desktop/src/setup.html"
|
||
to: "apps/desktop/src-tauri/src/lib.rs"
|
||
via: "window.__TAURI__.core.invoke('check_server' | 'save_server_url')"
|
||
pattern: "invoke\\('check_server'"
|
||
- from: "apps/desktop/src-tauri/src/lib.rs (Tray 'update')"
|
||
to: "apps/web/src/app/(portal)/settings/general/desktop/page.tsx"
|
||
via: "opener().open_url(`{server}/settings/general/desktop`)"
|
||
pattern: "settings/general/desktop"
|
||
---
|
||
|
||
<objective>
|
||
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.
|
||
</objective>
|
||
|
||
## 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>
|
||
|
||
<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
|
||
</context>
|
||
|
||
<tasks>
|
||
|
||
<task type="auto">
|
||
<name>Task 1: lib.rs — Kommandos fuer die Erststart-Seite, Versionspruefung gegen /desktop/latest, Tray mit Update und Autostart</name>
|
||
<precondition>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.</precondition>
|
||
<reversibility rating="reversible">Plugin-Einbindung und Tray-Aufbau sind lokal in einer Datei; ein Rueckbau ist ein Commit.</reversibility>
|
||
<files>
|
||
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
|
||
</files>
|
||
<read_first>
|
||
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)
|
||
</read_first>
|
||
<behavior>
|
||
- `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".
|
||
</behavior>
|
||
<action>
|
||
**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).
|
||
</action>
|
||
<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>
|
||
<verify>
|
||
<automated>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</automated>
|
||
<fails_when>`cargo check` oder `cargo clippy` endet nicht mit einer `Finished`-Zeile (Kompilier- oder Clippy-Fehler) — `RUST-OK` fehlt.</fails_when>
|
||
<automated>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</automated>
|
||
<fails_when>Eines der Kennzeichen (Kommandos, Rewrite-Praefix, Umlaut-Label, Autostart-Haken, Update-Link, Capability, Abhaengigkeit) fehlt — `WIRING-OK` fehlt.</fails_when>
|
||
</verify>
|
||
<done>
|
||
`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.
|
||
</done>
|
||
</task>
|
||
|
||
<task type="auto">
|
||
<name>Task 2: Erststart-Seite in Tessera-Gestalt und Sie-Form, echtes App-Icon, lokaler AppImage-Beweis</name>
|
||
<precondition>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.</precondition>
|
||
<files>
|
||
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
|
||
</files>
|
||
<read_first>
|
||
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)
|
||
</read_first>
|
||
<behavior>
|
||
- 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.
|
||
</behavior>
|
||
<action>
|
||
**`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 `<p id="info-msg">` 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).
|
||
</action>
|
||
<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>
|
||
<!-- planner-discipline-allow: unpkg.com -->
|
||
<!-- planner-discipline-allow: value="http://localhost:3000" -->
|
||
<verify>
|
||
<automated>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</automated>
|
||
<fails_when>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.</fails_when>
|
||
<automated>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</automated>
|
||
<fails_when>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.</fails_when>
|
||
</verify>
|
||
<done>
|
||
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/`.
|
||
</done>
|
||
</task>
|
||
|
||
</tasks>
|
||
|
||
<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>
|
||
|
||
<verification>
|
||
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).
|
||
</verification>
|
||
|
||
<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>
|
||
|
||
<output>
|
||
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.
|
||
</output>
|