--- phase: "18" slug: "desktop-client-fertigstellen" # status lifecycle: draft (seeded by plan-phase) → validated (set by validate-phase §6) # audit-milestone §5.5 distinguishes NOT-VALIDATED (draft) from PARTIAL (validated + nyquist_compliant: false) (#2117) status: draft nyquist_compliant: false wave_0_complete: false created: "2026-09-16" --- # Phase 18 — Validation Strategy > Per-phase validation contract for feedback sampling during execution. --- ## Test Infrastructure | Property | Value | |----------|-------| | **Framework** | Vitest 3.2.6 (`apps/api`, `environment: node`), Vitest 4.1.9 (`apps/web`, `environment: jsdom`), Cargo/Clippy 1.96 (`apps/desktop/src-tauri`), POSIX `sh -n` fuer CI-Skripte | | **Config file** | `apps/api/vitest.config.ts`, `apps/web/vitest.config.ts`, `apps/desktop/src-tauri/Cargo.toml` | | **Quick run command** | `pnpm --filter @tessera/api exec vitest run src/desktop` · `pnpm --filter @tessera/web exec vitest run src/lib/desktop.test.ts src/components/desktop src/components/settings/desktop-app-settings.test.tsx` · `cd apps/desktop/src-tauri && cargo check` | | **Full suite command** | `pnpm --filter @tessera/api exec vitest run && pnpm --filter @tessera/web exec vitest run && pnpm --filter @tessera/api type-check && pnpm --filter @tessera/web type-check` | | **Estimated runtime** | ~18 seconds (Quick), ~90 seconds (Full; Web-Suite 52 Dateien / 354 Tests am 2026-09-16 plus die neuen) | `biome check` ist kein Tor (bekannter Fehler in der Wurzel-`biome.json`, nicht anfassen). --- ## Sampling Rate - **After every task commit:** Run the quick command of the touched workspace (siehe Verification Map) - **After every plan wave:** Run `pnpm --filter @tessera/api exec vitest run && pnpm --filter @tessera/web exec vitest run` - **Before `/gsd-verify-work`:** Full suite must be green; zusaetzlich ein gruener Pipeline-Lauf mit beiden Paketen (18-05, Phasen-Tor per D-16) - **Max feedback latency:** 18 seconds (Quick); der lokale AppImage-Bau (18-01 T1/T2, 18-04 T2) und der Docker-Neubau (18-01 T1) sind bewusste Ausnahmen von mehreren Minuten --- ## Per-Task Verification Map | Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status | |---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------| | 18-01-01 | 01 | 1 | DESK-03, DESK-05 | T-18-01 / T-18-02 | Plattform-Whitelist vor Dateisystemzugriff; Dateiname nur aus Manifest; Namensmuster-Pruefung | HTTP-Durchstich (NestFactory) + Unit | `pnpm --filter @tessera/api exec vitest run src/desktop` | ❌ W0 (`apps/api/src/desktop/desktop.service.spec.ts`) | ⬜ pending | | 18-01-01 | 01 | 1 | DESK-03 | T-18-06 | Manifest-Hash stimmt mit Datei ueberein | Skript-Probe | `sh .gitea/scripts/desktop-collect.sh --require linux` + sha256-Vergleich | ✅ (Skript entsteht in der Task) | ⬜ pending | | 18-01-01 | 01 | 1 | DESK-03 | T-18-01 | Abbild liefert nur Manifest-Dateien, `attachment`-Header | Integration (lokaler Docker-Stack) | `curl -sf http://localhost:3001/desktop/latest` + Header-Check `/desktop/download/linux` + `/api-proxy/desktop/latest` | ✅ | ⬜ pending | | 18-01-02 | 01 | 1 | DESK-05 | — | Nur rein numerische Versionen werden geschrieben (NSIS) | Skript-Probe (positiv + negativ) | `sh .gitea/scripts/desktop-version.sh --print` = `1.1.0`; `DESKTOP_TAG=v1.2.3-beta … --print` endet mit Exit 1 | ✅ (Skript entsteht in der Task) | ⬜ pending | | 18-02-01 | 02 | 2 | DESK-01, DESK-04 | T-18-06 / T-18-21 | publish bricht ohne Manifest ab; kein upload-artifact; Cache-Schluessel exakt am SHA | Statisch (Workflow-Greps, `sh -n`, Probelauf) | `grep` auf `fail-on-cache-miss`, `needs: desktop`, `desktop-dist-${{ gitea.sha }}` (2x), `upload-artifact`=0; `publish-images.sh --print-plan` (4 push-Zeilen) | ✅ | ⬜ pending | | 18-02-02 | 02 | 2 | DESK-04 | T-18-03 | Token nur ueber Header-Datei, nie in einer curl-Zeile | Statisch (`sh -n`, Probelauf, Greps) | `sh -n publish-release.sh`; `publish-release.sh --dry-run --tag v1.1.0` nennt `assets?name=Tessera-1.1.0.AppImage`; `grep -c 'curl.*GITEA_TOKEN'`=0 | ✅ | ⬜ pending | | 18-03-01 | 03 | 2 | DESK-03 | T-18-07 | Linkziel nur aus `API_URL` + relativem `url` | Unit + Komponente | `pnpm --filter @tessera/web exec vitest run src/lib/desktop.test.ts src/components/desktop` | ❌ W0 (`apps/web/src/lib/desktop.test.ts`, `apps/web/src/components/desktop/desktop-download-links.test.tsx`) | ⬜ pending | | 18-03-02 | 03 | 2 | DESK-03 | T-18-08 | Hinweistext statt Knoepfe ohne Manifest; Text escaped | Komponente + i18n-Paritaet/Umlaut-Guard + Web-Suite | `pnpm --filter @tessera/web exec vitest run src/components/settings/desktop-app-settings.test.tsx …`; node-Paritaetsskript (`i18n OK`); `pnpm --filter @tessera/web exec vitest run` | ❌ W0 (`apps/web/src/components/settings/desktop-app-settings.test.tsx`) | ⬜ pending | | 18-04-01 | 04 | 2 | DESK-02, DESK-05 | T-18-10 / T-18-12 | Nur http/https; Opener nur mit gespeicherter `server_url`; Capability-Scope | Compile + Clippy + Kennzeichen-Greps | `cargo check && cargo clippy` (in `apps/desktop/src-tauri`); Greps auf `fn check_server`, `api-proxy`, `"Öffnen"`, `opener:allow-open-url` | ✅ (kein Vitest; Rust-Toolchain vorhanden) | ⬜ pending | | 18-04-02 | 04 | 2 | DESK-01, DESK-02 | T-18-11 | Kein Fremdcode in CSP; kein Modul-Import; keine vorbelegte Adresse | Statisch + lokaler Bau | Greps auf `window.__TAURI__.core`, `invoke('check_server'`, `unpkg.com`=0; `magick identify` Icon-Groessen; AppImage neuer als `lib.rs`; `desktop-collect.sh --require linux` | ✅ | ⬜ pending | | 18-05-01 | 05 | 3 | DESK-01, DESK-04 | T-18-15 | `--locked` Werkzeuginstallation; Reihenfolge AppImage vor NSIS | Statisch | Greps auf `cargo-xwin` (≥3), `--target x86_64-pc-windows-msvc --bundles nsis`, `--require linux,windows`; node-Reihenfolgepruefung | ✅ | ⬜ pending | | 18-05-02 | 05 | 3 | DESK-01, DESK-04, DESK-05 | T-18-18 | Secrets im Log maskiert | Manuell (Checkpoint: Orchestrator pusht und liest den Lauf) | — (human-action) | N/A | ⬜ pending | | 18-05-03 | 05 | 3 | DESK-01, DESK-04 | T-18-17 | Jede Runde ein Commit mit Ursache | Statisch + Compile | `sh -n` (drei Skripte); `cargo check`; `git rev-list --count --grep='ci(desktop): Runde' HEAD~6..HEAD` ≤ 3 | ✅ | ⬜ pending | | 18-06-01 | 06 | 4 | DESK-03, DESK-05 | T-18-19 / T-18-20 | SmartScreen-Hinweis an Herkunft gekoppelt; keine Firmenadresse | Doku-Greps | Greps auf `## Desktop-App`, `(#desktop-app)`, `Trotzdem ausführen`, ≥9 `###` im Kapitel, 0 Firmenadressen; CHANGELOG-Position (node) | ✅ | ⬜ pending | | 18-06-02 | 06 | 4 | DESK-04 | T-18-20 | Keine Firmenadresse in neuen Abschnitten | Doku-Greps | Greps auf `## 10. Desktop-App`, `DESKTOP_DIST_DIR`, `/app/desktop-dist`, `### Fehlerbilder`, `cargo-xwin` (≥2), `### Desktop-App lokal bauen`, `Tauri-Grundgerüst`=0 | ✅ | ⬜ pending | | 18-06-03 | 06 | 4 | DESK-01..05 | — | — | Gesamtlauf + Bedienprobe | `grep -c` DESK-Eintraege = 5 und Traceability = 5; Full suite + `cargo check` (`ALL-GREEN`) | ✅ | ⬜ pending | *Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky* --- ## Wave 0 Requirements - [ ] `apps/api/src/desktop/desktop.service.spec.ts` — HTTP-Durchstich ueber `NestFactory.create(DesktopModule)` mit echtem Temp-Verzeichnis: Manifest vorhanden (200), fehlt (404), unbekannte Plattform und Traversal (400, vor jedem Dateisystemzugriff), fehlende Plattform im Manifest (404), Manifest-Name mit Pfadzeichen (404), `@Public()`-Metadaten — entsteht in 18-01 Task 1 (DESK-03, DESK-05, T-18-01/02) - [ ] `apps/web/src/lib/desktop.test.ts` — memoisiertes Laden, still bei Fehler, `desktopDownloadUrl`, `formatFileSize` — 18-03 Task 1 (DESK-03) - [ ] `apps/web/src/components/desktop/desktop-download-links.test.tsx` — Link erscheint/verschwindet je nach API-Antwort, nur-Linux-Fall — 18-03 Task 1 (DESK-03, D-12) - [ ] `apps/web/src/components/settings/desktop-app-settings.test.tsx` — Version/Knoepfe/Groesse, Beta-Zeile, Hinweisfall — 18-03 Task 2 (DESK-03, D-12) - [ ] Framework install: none — Vitest ist in beiden Apps konfiguriert; Rust/Clippy und ImageMagick sind auf dem Entwicklungsrechner vorhanden (18-RESEARCH.md, Environment Availability; am 2026-09-16 geprueft) --- ## Manual-Only Verifications | Behavior | Requirement | Why Manual | Test Instructions | |----------|-------------|------------|-------------------| | Windows-NSIS-Cross-Bau erzeugt eine gueltige `.exe` | DESK-01, DESK-04 | Kein Windows-Werkzeug lokal (kein `makensis`, kein `cargo-xwin` auf dem Entwicklungsrechner); nur in der Pipeline beweisbar (D-16) | 18-05 Task 2: Orchestrator pusht, liest den Job `desktop`, meldet beide Dateizeilen aus "Pakete einsammeln"; Iterationsschleife max. 3 Runden | | Installer laeuft auf einem Windows-PC, Erststart zeigt die Anmeldung, Tray/Schliessen/Autostart/Beenden, Einstellungsseite | DESK-01, DESK-02, DESK-03 | Bedienung eines echten Windows-Systems | 18-06 Task 3 ``, Schritte 1-10 (Nutzer) | | Update-Hinweis bei neuerer Client-Version | DESK-05 | Braucht einen Server mit hoeherer Version als der installierte Client — erst nach dem naechsten Freigabe-Tag | 18-06 Task 3 `` Punkt (a): nach Tag `v1.2.0` zeigt der 1.1.0-Client die Benachrichtigung und den Menueeintrag "Version 1.2.0 herunterladen" | | Release-Dateien am Gitea-Release | DESK-04 | Upload laeuft nur bei Tags; ein Test-Tag wuerde den Live-Kanal ausloesen | 18-06 Task 3 `` Punkt (b): nach Tag `v1.2.0` traegt der Release `Tessera-Setup-1.2.0.exe` und `Tessera-1.2.0.AppImage`; bis dahin: `publish-release.sh --dry-run --tag v1.1.0` nennt die Uploads (18-02 Task 2) | --- ## Validation Sign-Off - [ ] All tasks have `` verify or Wave 0 dependencies - [ ] Sampling continuity: no 3 consecutive tasks without automated verify - [ ] Wave 0 covers all MISSING references - [ ] No watch-mode flags - [ ] Feedback latency < 18s - [ ] `nyquist_compliant: true` set in frontmatter **Approval:** pending