Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5afe2a4bc9 | |||
| 72e488eea4 | |||
| b42ba7ede5 | |||
| 3accc174b4 | |||
| 579e24b81a | |||
| 1b2f803c3e | |||
| 0d5c80fbbf | |||
| a8964f1a23 | |||
| 65efdf6ab7 | |||
| 54f396a288 | |||
| a777814034 | |||
| 29219baa60 | |||
| 8f2069b845 | |||
| 43c7061cb3 | |||
| b83d02d6fc | |||
| 1a05290841 |
@@ -62,6 +62,10 @@ jobs:
|
|||||||
desktop:
|
desktop:
|
||||||
name: Desktop-Pakete bauen
|
name: Desktop-Pakete bauen
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
# Commit-Stempel fuer build.rs (WR-02): mit Cargo-Zwischenspeicher wuerde
|
||||||
|
# `git rev-parse` im Build-Skript sonst nicht neu ausgewertet.
|
||||||
|
TESSERA_COMMIT: ${{ gitea.sha }}
|
||||||
needs: test
|
needs: test
|
||||||
if: gitea.ref == 'refs/heads/main' || startsWith(gitea.ref, 'refs/tags/v')
|
if: gitea.ref == 'refs/heads/main' || startsWith(gitea.ref, 'refs/tags/v')
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -81,6 +81,18 @@
|
|||||||
- [x] **PERM-06**: Die Migration überführt den Bestand ohne Zugriffsverlust: pro Mandant entsteht eine als Standardgruppe markierte Gruppe mit allen bestehenden Benutzern und Freigaben für alle zum Migrationszeitpunkt aktiven Module. Neue Benutzer — manuell angelegt wie per LDAP importiert — treten der markierten Standardgruppe automatisch bei.
|
- [x] **PERM-06**: Die Migration überführt den Bestand ohne Zugriffsverlust: pro Mandant entsteht eine als Standardgruppe markierte Gruppe mit allen bestehenden Benutzern und Freigaben für alle zum Migrationszeitpunkt aktiven Module. Neue Benutzer — manuell angelegt wie per LDAP importiert — treten der markierten Standardgruppe automatisch bei.
|
||||||
- [x] **PERM-07**: Ein Dashboard-Widget, dessen Modul dem Benutzer nicht freigegeben ist, erscheint nicht auf seinem Dashboard.
|
- [x] **PERM-07**: Ein Dashboard-Widget, dessen Modul dem Benutzer nicht freigegeben ist, erscheint nicht auf seinem Dashboard.
|
||||||
|
|
||||||
|
## Phase 18 — Desktop-Client fertigstellen
|
||||||
|
|
||||||
|
### DESK — Desktop-Client
|
||||||
|
|
||||||
|
**Hinzugefügt 2026-09-16** — DESK-01/02 stammen aus v1.0 (Phase 6) und werden fortgeführt; DESK-03..05 aus `18-CONTEXT.md` abgeleitet.
|
||||||
|
|
||||||
|
- [x] **DESK-01**: Tauri-basierter Desktop-Wrapper für Windows und Linux (Phase 6, fortgeführt).
|
||||||
|
- [x] **DESK-02**: Die Desktop-App verbindet sich mit dem Web-Backend; die Server-Adresse wird beim ersten Start abgefragt (Phase 6, fortgeführt; D-02).
|
||||||
|
- [ ] **DESK-03**: Der Installer ist in Tessera herunterladbar — Link auf der Anmeldeseite und Seite Einstellungen → Desktop-App, Auslieferung über die Tessera-API ohne Gitea-Zugang (D-01, D-10, D-12).
|
||||||
|
- [ ] **DESK-04**: Ein Freigabe-Tag baut Windows-Installer und Linux-AppImage in der Pipeline und hängt beide als Dateien an den Gitea-Release (D-04..D-08).
|
||||||
|
- [ ] **DESK-05**: Der Client trägt die Freigabe-Version, vergleicht sie mit `/desktop/latest` und weist mit Download-Link auf eine neuere Version hin (D-07, D-11, D-13).
|
||||||
|
|
||||||
## Future Requirements (deferred)
|
## Future Requirements (deferred)
|
||||||
|
|
||||||
- [ ] TED API v3 (EU-weite Redundanz) — für DE-only weitgehend redundant zu DÖE.
|
- [ ] TED API v3 (EU-weite Redundanz) — für DE-only weitgehend redundant zu DÖE.
|
||||||
@@ -145,4 +157,10 @@
|
|||||||
| PERM-06 | Phase 15 | Pending |
|
| PERM-06 | Phase 15 | Pending |
|
||||||
| PERM-07 | Phase 15 | Complete |
|
| PERM-07 | Phase 15 | Complete |
|
||||||
|
|
||||||
**Coverage:** 34/34 v1.1 requirements mapped (29 aus Phase 10–14 + SRC-01..05 aus Phase 17, nachträglich am 2026-08-12 ergänzt — die Kategorie fehlte in dieser Datei, obwohl 17-01/17-02 sie bereits in ihren SUMMARY-Frontmattern als erledigt führten) — no orphans. 7/7 v1.2 requirements mapped: PERM-01/03/04/05/06/07 auf Phase 15, PERM-02 auf Phase 16 (umgehängt 2026-08-06).
|
| DESK-01 | Phase 6 / 18 | Complete |
|
||||||
|
| DESK-02 | Phase 6 / 18 | Complete |
|
||||||
|
| DESK-03 | Phase 18 | Pending |
|
||||||
|
| DESK-04 | Phase 18 | Pending |
|
||||||
|
| DESK-05 | Phase 18 | Pending |
|
||||||
|
|
||||||
|
**Coverage:** 34/34 v1.1 requirements mapped (29 aus Phase 10–14 + SRC-01..05 aus Phase 17, nachträglich am 2026-08-12 ergänzt — die Kategorie fehlte in dieser Datei, obwohl 17-01/17-02 sie bereits in ihren SUMMARY-Frontmattern als erledigt führten) — no orphans. 7/7 v1.2 requirements mapped: PERM-01/03/04/05/06/07 auf Phase 15, PERM-02 auf Phase 16 (umgehängt 2026-08-06). 5/5 DESK-Anforderungen auf Phase 18 abgebildet (DESK-01/02 fortgeführt aus Phase 6, DESK-03/04/05 neu in Phase 18) — DESK-03..05 wechseln auf Complete, sobald die Bedienprobe dieser Phase abgeschlossen ist.
|
||||||
|
|||||||
@@ -666,7 +666,7 @@ Plans (Wellenstruktur — streng nacheinander, alle drei fassen Schema, Controll
|
|||||||
3. Der installierte Client zeigt nach Eingabe der Server-Adresse die Tessera-Anmeldung, laeuft mit Tray/Schliessen-ins-Tray/Autostart wie in Phase 6 und meldet eine neuere Client-Version mit Link zur Download-Seite
|
3. Der installierte Client zeigt nach Eingabe der Server-Adresse die Tessera-Anmeldung, laeuft mit Tray/Schliessen-ins-Tray/Autostart wie in Phase 6 und meldet eine neuere Client-Version mit Link zur Download-Seite
|
||||||
4. Anwender- und Betriebshandbuch beschreiben Installation, Erststart, Tray-Verhalten, Pipeline, Release-Dateien und Umgebungsvariablen
|
4. Anwender- und Betriebshandbuch beschreiben Installation, Erststart, Tray-Verhalten, Pipeline, Release-Dateien und Umgebungsvariablen
|
||||||
|
|
||||||
**Plans:** 4/6 plans executed
|
**Plans:** 6/6 plans executed
|
||||||
|
|
||||||
Plans:
|
Plans:
|
||||||
**Wave 1**
|
**Wave 1**
|
||||||
@@ -681,8 +681,8 @@ Plans:
|
|||||||
|
|
||||||
**Wave 3** *(blocked on Wave 2 completion)*
|
**Wave 3** *(blocked on Wave 2 completion)*
|
||||||
|
|
||||||
- [ ] 18-05-PLAN.md — Windows-Cross-Bau (Welle 3): cargo-xwin/NSIS im Job desktop, Einsammeln beider Pakete, Push-Checkpoint mit Iterationsschleife (max. 3 Runden) bis zum gruenen Lauf
|
- [x] 18-05-PLAN.md — Windows-Cross-Bau (Welle 3): cargo-xwin/NSIS im Job desktop, Einsammeln beider Pakete, Push-Checkpoint mit Iterationsschleife (max. 3 Runden) bis zum gruenen Lauf
|
||||||
|
|
||||||
**Wave 4** *(blocked on Wave 3 completion)*
|
**Wave 4** *(blocked on Wave 3 completion)*
|
||||||
|
|
||||||
- [ ] 18-06-PLAN.md — Abschluss (Welle 4): Handbuecher (Anwender, Betrieb, Entwicklung, CI/CD-Runbook), CHANGELOG, REQUIREMENTS DESK-01..05, Gesamtlaeufe, Bedienprobe des Nutzers auf Windows
|
- [x] 18-06-PLAN.md — Abschluss (Welle 4): Handbuecher (Anwender, Betrieb, Entwicklung, CI/CD-Runbook), CHANGELOG, REQUIREMENTS DESK-01..05, Gesamtlaeufe, Bedienprobe des Nutzers auf Windows
|
||||||
|
|||||||
+12
-8
@@ -4,16 +4,16 @@ milestone: v1.2
|
|||||||
current_phase: 18
|
current_phase: 18
|
||||||
current_phase_name: desktop-client-fertigstellen
|
current_phase_name: desktop-client-fertigstellen
|
||||||
status: verified
|
status: verified
|
||||||
stopped_at: Completed 18-04-PLAN.md
|
stopped_at: Completed 18-06-PLAN.md — Windows-Bedienprobe des Nutzers steht aus
|
||||||
last_updated: "2026-09-16T14:43:25.735Z"
|
last_updated: "2026-09-16T15:27:16.943Z"
|
||||||
last_activity: 2026-09-16
|
last_activity: 2026-09-16
|
||||||
last_activity_desc: Quick 260910-jab — drei zu kurz greifende RLS-Regeln geschlossen (GroupMembership beide Seiten, ModuleGrant beide Ziele, TenderRssFeedSource Lese-/Schreibsplit), listForUser gebunden, Aktenstand kohaerent
|
last_activity_desc: Quick 260910-jab — drei zu kurz greifende RLS-Regeln geschlossen (GroupMembership beide Seiten, ModuleGrant beide Ziele, TenderRssFeedSource Lese-/Schreibsplit), listForUser gebunden, Aktenstand kohaerent
|
||||||
state_head: 289604a28e326f9909963922ca938c6a8fa7d661
|
state_head: a777814034ee5907cb4d151669447f015841082d
|
||||||
progress:
|
progress:
|
||||||
total_phases: 18
|
total_phases: 18
|
||||||
completed_phases: 15
|
completed_phases: 15
|
||||||
total_plans: 89
|
total_plans: 89
|
||||||
completed_plans: 86
|
completed_plans: 88
|
||||||
milestone_name: Plattform-Berechtigungen
|
milestone_name: Plattform-Berechtigungen
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -29,9 +29,9 @@ See: .planning/PROJECT.md (updated 2026-07-17)
|
|||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 18 (desktop-client-fertigstellen) — IN PROGRESS
|
Phase: 18 (desktop-client-fertigstellen) — IN PROGRESS
|
||||||
Plan: 4 of 6 (18-02 abgeschlossen)
|
Plan: 6 of 6 (18-02 abgeschlossen)
|
||||||
Status: 18-02 (CI-Job desktop, Cache-Uebergabe an publish, Release-Anhaenge) fertig; 18-03 (Web-Oberflaeche), 18-04 (Client-Updatepruefung), 18-05 (Windows-Cross-Bau + Pipeline-Beweis), 18-06 (Freigabe) stehen aus
|
Status: 18-02 (CI-Job desktop, Cache-Uebergabe an publish, Release-Anhaenge) fertig; 18-03 (Web-Oberflaeche), 18-04 (Client-Updatepruefung), 18-05 (Windows-Cross-Bau + Pipeline-Beweis), 18-06 (Freigabe) stehen aus
|
||||||
Last activity: 2026-09-16 - 18-02: Job desktop (Linux-AppImage, actions/cache) vor publish, Manifest-Pruefung in publish-images.sh, idempotenter Release-Anhang-Upload in publish-release.sh
|
Last activity: 2026-09-16 - Phase 18 Desktop-Client: 6/6 Plaene ausgefuehrt, Code-Review behoben, Verifikation human_needed (Windows-Bedienprobe durch den User, Release-Anhang + Update-Hinweis erst beim naechsten Tag); gepusht, CI-Lauf 367 gruen mit beiden Paketen
|
||||||
|
|
||||||
Progress: [███░░░░░░░] 33%
|
Progress: [███░░░░░░░] 33%
|
||||||
|
|
||||||
@@ -127,6 +127,8 @@ Progress: [███░░░░░░░] 33%
|
|||||||
| Phase 18 P02 | 8 min | 2 tasks | 3 files |
|
| Phase 18 P02 | 8 min | 2 tasks | 3 files |
|
||||||
| Phase 18-desktop-client-fertigstellen P03 | 20 min | 2 tasks | 12 files |
|
| Phase 18-desktop-client-fertigstellen P03 | 20 min | 2 tasks | 12 files |
|
||||||
| Phase 18 P04 | 9min | 2 tasks | 15 files |
|
| Phase 18 P04 | 9min | 2 tasks | 15 files |
|
||||||
|
| Phase 18 P05 | 29 min | 3 tasks | 1 files |
|
||||||
|
| Phase 18 P06 | 21min | 3 tasks | 7 files |
|
||||||
|
|
||||||
## Accumulated Context
|
## Accumulated Context
|
||||||
|
|
||||||
@@ -315,6 +317,8 @@ Recent decisions affecting current work:
|
|||||||
- [Phase 18]: 18-02: Cross-Job-Uebergabe per actions/cache (save/restore, Schluessel exakt am gitea.sha) statt upload-/download-artifact, da diese auf der Gitea-Instanz unzuverlaessig sind. — Pitfall 1 aus 18-RESEARCH.md; publish bricht bei Cache-Fehlschlag hart ab (fail-on-cache-miss + explizite Manifest-Pruefung in Workflow und Skript).
|
- [Phase 18]: 18-02: Cross-Job-Uebergabe per actions/cache (save/restore, Schluessel exakt am gitea.sha) statt upload-/download-artifact, da diese auf der Gitea-Instanz unzuverlaessig sind. — Pitfall 1 aus 18-RESEARCH.md; publish bricht bei Cache-Fehlschlag hart ab (fail-on-cache-miss + explizite Manifest-Pruefung in Workflow und Skript).
|
||||||
- [Phase 18]: Desktop-Download-Link/Einstellungsseite lesen GET /desktop/latest memoisiert und blenden sich ohne Pakete aus — Wiederverwendung des app-version.ts-Musters (Modul-Ebene-Promise, still bei Fehler)
|
- [Phase 18]: Desktop-Download-Link/Einstellungsseite lesen GET /desktop/latest memoisiert und blenden sich ohne Pakete aus — Wiederverwendung des app-version.ts-Musters (Modul-Ebene-Promise, still bei Fehler)
|
||||||
- [Phase 18]: 18-04: api_url() als einzige Stelle mit dem /api-proxy-Rewrite-Praefix; Erststart-Seite spricht nur noch ueber window.__TAURI__.core.invoke statt Modul-Import
|
- [Phase 18]: 18-04: api_url() als einzige Stelle mit dem /api-proxy-Rewrite-Praefix; Erststart-Seite spricht nur noch ueber window.__TAURI__.core.invoke statt Modul-Import
|
||||||
|
- [Phase 18]: Windows-Werkzeuge-Schritt nach dem Cargo-Zwischenspeicher platziert, nicht davor — Damit die cargo-xwin-Installationspruefung (command -v ...) den per actions/cache wiederhergestellten Stand sieht und das Werkzeug bei warmem Cache nicht bei jedem Lauf neu gebaut wird.
|
||||||
|
- [Phase 18]: 18-06: DESK-03/04/05 bleiben Pending bis Windows-Bedienprobe des Nutzers vorliegt; alle Handbuecher/CHANGELOG/REQUIREMENTS nachgezogen, alle Gesamtlaeufe gruen (API 1086, Web 365, Typpruefungen, cargo check).
|
||||||
|
|
||||||
### Pitfalls & Anti-Patterns
|
### Pitfalls & Anti-Patterns
|
||||||
|
|
||||||
@@ -467,8 +471,8 @@ sind. Kein Anlass, sie vorher erneut vorzulegen.
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-09-16T14:43:25.471Z
|
Last session: 2026-09-16T15:27:16.618Z
|
||||||
Resumed: 2026-09-14 — Sitzung ueber /gsd-resume-work fortgesetzt; #29 und 3c als /gsd-quick --validate mit voller Kette durchgefuehrt.
|
Resumed: 2026-09-14 — Sitzung ueber /gsd-resume-work fortgesetzt; #29 und 3c als /gsd-quick --validate mit voller Kette durchgefuehrt.
|
||||||
Stopped at: Completed 18-04-PLAN.md
|
Stopped at: Completed 18-06-PLAN.md — Windows-Bedienprobe des Nutzers steht aus
|
||||||
Resume file: None
|
Resume file: None
|
||||||
Last activity: 2026-09-16 - Completed quick task 260916-k2z: Mehrfach-Kreise je Kalender am Tag; Browser-Pruefung + Push ausstehend
|
Last activity: 2026-09-16 - Completed quick task 260916-k2z: Mehrfach-Kreise je Kalender am Tag; Browser-Pruefung + Push ausstehend
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
---
|
||||||
|
phase: 18-desktop-client-fertigstellen
|
||||||
|
plan: 05
|
||||||
|
subsystem: infra
|
||||||
|
tags: [ci, gitea-actions, tauri, cargo-xwin, nsis, windows-cross-build, desktop]
|
||||||
|
|
||||||
|
# Dependency graph
|
||||||
|
requires:
|
||||||
|
- phase: 18-desktop-client-fertigstellen (18-02)
|
||||||
|
provides: Job `desktop` mit Linux-AppImage-Bau, `actions/cache`-Uebergabe an `publish`, `desktop-collect.sh`/`publish-release.sh`
|
||||||
|
- phase: 18-desktop-client-fertigstellen (18-04)
|
||||||
|
provides: Client mit tauri-plugin-opener/autostart/window-state — die Cargo.toml, die der Cross-Bau kompilieren muss
|
||||||
|
provides:
|
||||||
|
- Job `desktop` baut auf dem Linux-Runner zusaetzlich `Tessera-Setup-X.Y.Z.exe` per Cross-Bau (cargo-xwin, NSIS)
|
||||||
|
- Gruener Pipeline-Lauf auf `main` mit beiden Dateien (`.exe` + `.AppImage`), gruenem `publish`
|
||||||
|
affects: [18-06]
|
||||||
|
|
||||||
|
# Actuals (#2632)
|
||||||
|
actuals:
|
||||||
|
tokens: 900
|
||||||
|
tasks: 3
|
||||||
|
commits: 2
|
||||||
|
|
||||||
|
# Tech tracking
|
||||||
|
tech-stack:
|
||||||
|
added: [cargo-xwin 0.23.x (Ubuntu apt: lld/llvm/clang/nsis)]
|
||||||
|
patterns:
|
||||||
|
- "Cross-Job-Handoff ueber actions/cache@v4 (Schluessel gitea.sha), nicht upload-artifact — bestaetigt auch fuer den erweiterten Job funktionsfaehig"
|
||||||
|
- "Cache-Restore-Schritt steht VOR jedem Installationsschritt, der geprueft werden soll (command -v ... || cargo install), sonst greift die Restauration zu spaet und das Werkzeug wird bei jedem Lauf neu gebaut"
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created: []
|
||||||
|
modified:
|
||||||
|
- .gitea/workflows/ci.yml
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Windows-Bau laeuft nacheinander im selben Job wie der Linux-Bau (ein Cache, ein Runner) statt in einem eigenen Job — wie in 18-CONTEXT.md (Specific Ideas, Runner-Grenzen 8 Kerne/15 GB) festgelegt."
|
||||||
|
- "cargo-xwin, NSIS-Plugin-Ablage (~/.local/share/tauri) und die xwin-SDK-Ablage (~/.cache/cargo-xwin) wurden in den bestehenden Cargo-Zwischenspeicher-Schritt aufgenommen statt einen zweiten Cache-Schritt anzulegen."
|
||||||
|
- "Der Schritt 'Windows-Werkzeuge' (rustup target add + cargo install cargo-xwin) wurde bewusst NACH dem Cargo-Zwischenspeicher-Schritt platziert (nicht wie im Plantext 'nach Rust-Toolchain' woertlich als naechster Schritt), damit die command -v cargo-xwin-Pruefung den wiederhergestellten Cache sieht und das Werkzeug bei warmem Cache nicht jedes Mal neu gebaut wird."
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "Iterationsschleife (D-16): jede Runde ein eigener Commit mit Praefix 'ci(desktop): Runde N — {Ursache}', Ursache/Fix im SUMMARY dokumentiert."
|
||||||
|
|
||||||
|
requirements-completed: [DESK-01, DESK-04, DESK-05]
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
- id: D1
|
||||||
|
description: "Der CI-Job desktop baut auf dem Linux-Runner den Windows-Installer per Cross-Bau (cargo-xwin, NSIS) und sammelt Tessera-Setup-X.Y.Z.exe neben Tessera-X.Y.Z.AppImage ein"
|
||||||
|
requirement: "DESK-04"
|
||||||
|
verification:
|
||||||
|
- kind: e2e
|
||||||
|
ref: "Gitea CI/CD Lauf 367 (Commit 742fb5c), Job 'Desktop-Pakete bauen', Schritt 'Pakete einsammeln'"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D2
|
||||||
|
description: "Ein Push auf main endet mit einem gruenen Lauf: Job desktop mit beiden Dateien, Job publish mit Abbildern, die die Pakete tragen"
|
||||||
|
requirement: "DESK-04"
|
||||||
|
verification:
|
||||||
|
- kind: e2e
|
||||||
|
ref: "Gitea CI/CD Lauf 367 (Commit 742fb5c) — alle vier Jobs gruen, Job publish hat Abbilder mit Etiketten beta/latest gepusht"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D3
|
||||||
|
description: "Jeder Fehlschlag der Pipeline wird gelesen, der Job angepasst, erneut gepusht — hoechstens drei Runden, jede als normaler Commit"
|
||||||
|
requirement: "DESK-01"
|
||||||
|
verification:
|
||||||
|
- kind: manual_procedural
|
||||||
|
ref: "Runde 1 (Commit 742fb5c): clippy-Komponente fehlte, ein Commit, danach gruen — 1 von hoechstens 3 Runden verbraucht"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
|
||||||
|
duration: 29min
|
||||||
|
completed: 2026-09-16
|
||||||
|
status: complete
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 18 Plan 05: Windows-Cross-Bau im CI-Job desktop Summary
|
||||||
|
|
||||||
|
**Der CI-Job `desktop` baut jetzt in einem Lauf auf dem Linux-Runner sowohl das Linux-AppImage als auch — per Cross-Bau mit `cargo-xwin` und NSIS aus dem Ubuntu-Paket — den Windows-Installer; bewiesen durch einen gruenen Pipeline-Lauf mit beiden Dateien und gruenem `publish`.**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** 29 min
|
||||||
|
- **Started:** 2026-09-16T14:46:59Z (erster Commit dieses Plans)
|
||||||
|
- **Completed:** 2026-09-16T15:15:23Z
|
||||||
|
- **Tasks:** 3 (Task 2 = Checkpoint, kein eigener Commit; 3 Aufgaben, 2 Commits)
|
||||||
|
- **Files modified:** 1 (`.gitea/workflows/ci.yml`)
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
- Job `desktop` installiert `lld llvm clang nsis` per apt und ein neues `rustup target x86_64-pc-windows-msvc` + `cargo-xwin` (Schritt "Windows-Werkzeuge", NACH dem Cache-Restore platziert, damit ein warmer Cache das Neu-Bauen von `cargo-xwin` erspart).
|
||||||
|
- Nach dem bestehenden AppImage-Schritt baut ein neuer Schritt "Windows-Installer bauen (Cross-Bau)" per `pnpm --filter @tessera/desktop exec tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis`.
|
||||||
|
- "Pakete einsammeln" laeuft jetzt mit `--require linux,windows`; `desktop-collect.sh` kannte den Windows-Zweig (`target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe`) bereits aus 18-01, keine Skript-Aenderung noetig.
|
||||||
|
- Cargo-Zwischenspeicher um `~/.cargo/bin/cargo-xwin`, `~/.cache/cargo-xwin` (Windows-SDK-Ablage von cargo-xwin) und `~/.local/share/tauri` (NSIS-Plugins) erweitert.
|
||||||
|
- Iterationsschleife durchlaufen (D-16): 1 Runde — fehlende `clippy`-Komponente im `--profile minimal`-Toolchain behoben, dann gruener Lauf.
|
||||||
|
- Gruener Pipeline-Lauf auf `main` (Lauf 367, Commit `742fb5c`): alle vier Jobs gruen, Job `desktop` in ~10 min (kalter Cache) mit `Tessera-Setup-1.1.0-beta.742fb5c.exe` (2.775.663 Bytes) und `Tessera-1.1.0-beta.742fb5c.AppImage` (82.479.608 Bytes), Job `publish` hat Abbilder mit Etiketten `beta`/`latest` gepusht.
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
Jede Aufgabe wurde atomar committet:
|
||||||
|
|
||||||
|
1. **Task 1: Windows-Cross-Bau in den Job desktop einbauen** - `1c4247a` (ci)
|
||||||
|
2. **Task 2: Push und CI-Lauf beobachten** - Checkpoint, kein eigener Commit (Push durch Orchestrator, kein Repo-Zustand veraendert)
|
||||||
|
3. **Task 3, Runde 1: clippy-Komponente ergaenzt** - `742fb5c` (ci)
|
||||||
|
|
||||||
|
**Plan metadata:** folgt in diesem Commit (`docs(18-05): ...`)
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
- `.gitea/workflows/ci.yml` - Windows-Cross-Bau-Schritte im Job `desktop` (apt-Pakete, Werkzeuge, Cache-Pfade, Bauschritt, Einsammeln mit beiden Plattformen), plus die Runde-1-Korrektur (`rustup component add clippy`)
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
- Windows-Bau nacheinander im selben Job wie der Linux-Bau, ein Cache — wie in `18-CONTEXT.md` (Specific Ideas) festgelegt, statt eines zweiten parallelen Jobs, der die Runner-Grenzen (8 Kerne/15 GB) ueberschritten haette.
|
||||||
|
- Der neue Schritt "Windows-Werkzeuge" wurde entgegen der woertlichen Plan-Reihenfolge ("nach Rust-Toolchain") NACH dem Cargo-Zwischenspeicher-Schritt platziert. Grund: `command -v cargo-xwin >/dev/null 2>&1 || cargo install --locked cargo-xwin` soll den wiederhergestellten Cache sehen koennen — stuende der Schritt vor dem Cache-Restore, waere `cargo-xwin` bei jedem Lauf neu gebaut, selbst wenn der Cache es bereits enthaelt. Das Ziel des Plans ("SDK-Ablage ... soll nur einmal geladen werden") war damit nur durch die Umstellung der Reihenfolge sauber erreichbar; die genannten Kennzeichen des Plans (`cargo-xwin`-Vorkommen, Cache-Pfade, Bauschritt-Reihenfolge AppImage-vor-NSIS) blieben davon unberuehrt und alle automatisierten `<verify>`-Pruefungen bestehen weiterhin.
|
||||||
|
- Keine `XWIN_CACHE_DIR`-Umgebungsvariable im ersten Anlauf gesetzt (Plan-Vorgabe: das ist eine Ausweichlösung der Iterationsschleife, nicht Teil von Task 1) — nicht noetig, der Lauf war nach der Clippy-Korrektur gruen.
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Auto-fixed Issues
|
||||||
|
|
||||||
|
**1. [Rule 3 - Blocking] `cargo clippy` scheiterte an fehlender clippy-Komponente**
|
||||||
|
- **Found during:** Task 2 (erste CI-Rueckmeldung, "rot")
|
||||||
|
- **Issue:** Der Schritt "Rust-Toolchain" installiert `stable` mit `--profile minimal`, dieses Profil enthaelt `clippy` nicht. Der bereits bestehende Schritt "Rust pruefen" (aus 18-02, unveraendert von diesem Plan) ruft `cargo clippy` auf und scheiterte mit `'cargo-clippy' is not installed for the toolchain 'stable-x86_64-unknown-linux-gnu'`.
|
||||||
|
- **Fix:** `"$HOME/.cargo/bin/rustup" component add clippy` direkt nach der Toolchain-Installation im Schritt "Rust-Toolchain" ergaenzt (voller Pfad, weil `$GITHUB_PATH` erst fuer Folgeschritte greift, nicht innerhalb desselben `run:`-Blocks).
|
||||||
|
- **Files modified:** `.gitea/workflows/ci.yml`
|
||||||
|
- **Verification:** Lauf 367 (Commit `742fb5c`) — Schritt "Rust pruefen" gruen, gesamter Job `desktop` gruen.
|
||||||
|
- **Committed in:** `742fb5c` (`ci(desktop): Runde 1 — clippy-Komponente fehlt im Toolchain`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Total deviations:** 1 auto-fixed (1 blocking, per D-16-Iterationsschleife — dies ist die im Plan vorgesehene Korrekturrunde, keine ungeplante Abweichung im engeren Sinn).
|
||||||
|
**Impact on plan:** Notwendige Korrektur fuer einen gruenen Lauf; kein Scope-Creep, betraf ausschliesslich den bereits vorhandenen "Rust pruefen"-Schritt aus 18-02.
|
||||||
|
|
||||||
|
## Iterationsschleife (D-16)
|
||||||
|
|
||||||
|
| Runde | Signatur im Protokoll | Ursache | Aenderung | Commit |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| 1 | `error: 'cargo-clippy' is not installed for the toolchain 'stable-x86_64-unknown-linux-gnu'` (Job `desktop`, Schritt "Rust pruefen", Lauf 366) | `--profile minimal` installiert keine `clippy`-Komponente | `rustup component add clippy` im Schritt "Rust-Toolchain" | `742fb5c` |
|
||||||
|
|
||||||
|
Nach Runde 1: Lauf 367 (Commit `742fb5c`) komplett gruen — Iterationsschleife nach 1 von hoechstens 3 Runden beendet.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
Keiner ueber die dokumentierte Runde 1 hinaus. Hinweis aus der Rueckmeldung des roten Laufs: nach dem Fehlschlag in Runde-0 (Lauf 366) landete nichts im Cargo-Cache (Post-Schritt wegen `success()=false` uebersprungen) — der naechste Lauf startete entsprechend kalt (~10 min fuer den Job `desktop`). Kein Fehler, nur zur Einordnung der Bau-Dauer.
|
||||||
|
|
||||||
|
## User Setup Required
|
||||||
|
None - keine externe Dienstkonfiguration noetig.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
- Beide Desktop-Pakete (`.exe`, `.AppImage`) entstehen zuverlaessig in einem CI-Lauf auf `main`; das Beta-Abbild traegt sie ab Lauf 367.
|
||||||
|
- 18-06 kann den Testserver auf den neuen Stand ziehen und die Pakete dort sichtbar machen (Download-Weg selbst wurde bereits in 18-02 gebaut und per Probelauf geprueft).
|
||||||
|
- Der Release-Anhang (Gitea-Release-Datei-Upload) ist erst beim naechsten Freigabe-Tag `v*` sichtbar — in diesem Lauf war kein Tag gesetzt, der Release-Schritt lief erwartungsgemaess nicht.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 18-desktop-client-fertigstellen*
|
||||||
|
*Completed: 2026-09-16*
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
- FOUND: .planning/phases/18-desktop-client-fertigstellen/18-05-SUMMARY.md
|
||||||
|
- FOUND: 1c4247a (Task 1)
|
||||||
|
- FOUND: 742fb5c (Task 3 Runde 1)
|
||||||
|
- Gruener CI-Lauf 367 (Commit 742fb5c) durch den Orchestrator bestaetigt, `git status --porcelain` leer, `git log -1` entspricht dem gemeldeten Lauf-Commit.
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
---
|
||||||
|
phase: 18-desktop-client-fertigstellen
|
||||||
|
plan: 06
|
||||||
|
subsystem: docs
|
||||||
|
tags: [documentation, changelog, requirements-traceability, desktop-distribution]
|
||||||
|
|
||||||
|
# Dependency graph
|
||||||
|
requires:
|
||||||
|
- phase: 18-desktop-client-fertigstellen (18-01..18-05)
|
||||||
|
provides: "GET /desktop/latest, GET /desktop/download/:platform, Web-Oberflaeche (Anmeldeseite/Einstellungen), Client-Update-Hinweis/Tray/Autostart, CI-Job desktop mit Windows-Cross-Bau (Lauf 367, Commit 742fb5c)"
|
||||||
|
provides:
|
||||||
|
- "Anwenderhandbuch Kapitel 'Desktop-App' (9 Unterabschnitte: Was es ist, Herunterladen, Installation Windows/Linux, Erster Start, Infobereich/Beenden, Automatischer Start, Neue Version, Fehlerbilder)"
|
||||||
|
- "Betriebshandbuch Kapitel 10 (Pipeline-Herkunft, Ablageort im Abbild, Release-Anhaenge, DESKTOP_DIST_DIR, Fehlerbilder)"
|
||||||
|
- "CI/CD-Runbook: Job desktop dokumentiert (Cross-Bau, Cache-Reihenfolge, Cache-vs-upload-artifact, drei neue Fehlerbehebungs-Unterabschnitte)"
|
||||||
|
- "Entwicklungshandbuch: apps/desktop nicht mehr als Grundgeruest, Abschnitt 'Desktop-App lokal bauen', Testabschnitt um vitest src/desktop + cargo check/clippy ergaenzt"
|
||||||
|
- "CHANGELOG-Stichpunkt (D-17), REQUIREMENTS.md Kategorie DESK mit Traceability"
|
||||||
|
affects: []
|
||||||
|
|
||||||
|
actuals:
|
||||||
|
tokens: 7150
|
||||||
|
tasks: 3
|
||||||
|
commits: 3
|
||||||
|
plan_head_before: b83d02d
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns: []
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- .planning/phases/18-desktop-client-fertigstellen/18-06-SUMMARY.md
|
||||||
|
modified:
|
||||||
|
- docs/anleitung-anwender.md
|
||||||
|
- docs/anleitung-betrieb.md
|
||||||
|
- docs/anleitung-entwicklung.md
|
||||||
|
- docs/ci-cd-setup.md
|
||||||
|
- CHANGELOG.md
|
||||||
|
- .planning/REQUIREMENTS.md
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "DESK-03/04/05 bleiben in REQUIREMENTS.md auf Pending stehen, bis die Bedienprobe des Nutzers (Windows-Installation) tatsaechlich durchgefuehrt wurde — dieser Plan liefert die Dokumentation und alle automatisierten Gesamtlaeufe, kann die Bedienprobe selbst aber nicht ausfuehren (kein Windows-PC in dieser Ausfuehrungsumgebung)."
|
||||||
|
- "Kapitel 9 (Betriebshandbuch, 'Eine Version freigeben') um einen Verweis-Satz auf Kapitel 10 ergaenzt, statt Kapitel 10 isoliert stehen zu lassen — der Freigabe-Ablauf und der Desktop-Release-Anhang gehoeren fachlich zusammen."
|
||||||
|
|
||||||
|
patterns-established: []
|
||||||
|
|
||||||
|
requirements-completed: [DESK-01, DESK-02, DESK-03, DESK-04, DESK-05]
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
- id: D1
|
||||||
|
description: "Anwenderhandbuch: Kapitel 'Desktop-App' mit neun Unterabschnitten (Was es ist, Herunterladen, Installation Windows/Linux inkl. SmartScreen-Anleitung, Erster Start, Infobereich/Beenden, Automatischer Start, Neue Version, Fehlerbilder), Inhaltsverzeichnis-Eintrag, keine firmenspezifische Adresse im Kapitel"
|
||||||
|
requirement: "DESK-03"
|
||||||
|
verification:
|
||||||
|
- kind: other
|
||||||
|
ref: "DOCS1-OK Pruefkette aus 18-06-PLAN.md <verify> (Kapitel, TOC-Anker, 9 Unterabschnitte, Pflichtbeschriftungen 'Trotzdem ausführen'/'Desktop-App herunterladen (Windows)'/'Mit Windows starten', 0 ctl.de/vicolab-Treffer im Kapitel)"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D2
|
||||||
|
description: "CHANGELOG-Stichpunkt im Wortlaut von D-17 unter Unveröffentlicht/Neu, oberhalb der ersten Versionsueberschrift"
|
||||||
|
requirement: "DESK-03"
|
||||||
|
verification:
|
||||||
|
- kind: other
|
||||||
|
ref: "node-Pruefung aus 18-06-PLAN.md <verify> (Position zwischen '## Unveröffentlicht' und der naechsten Versionsueberschrift)"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D3
|
||||||
|
description: "Betriebshandbuch Kapitel 10 (Pipeline-Herkunft, Ablageort /app/desktop-dist im Abbild, Release-Anhaenge, DESKTOP_DIST_DIR-Tabelle, Fehlerbilder-Tabelle) plus Verweissatz in Kapitel 9"
|
||||||
|
requirement: "DESK-04"
|
||||||
|
verification:
|
||||||
|
- kind: other
|
||||||
|
ref: "BETRIEB-Pruefkette aus 18-06-PLAN.md <verify> (Kapitelueberschrift, TOC-Eintrag '10. [', DESKTOP_DIST_DIR, /app/desktop-dist, Abschnitt Fehlerbilder)"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D4
|
||||||
|
description: "CI/CD-Runbook: 'vier' statt 'drei' Jobs, Job desktop ausfuehrlich beschrieben (Cross-Bau-Reihenfolge, Cache-Pfade, Cache-Schluessel desktop-dist-{sha}, Begruendung Cache statt upload-artifact), drei neue Fehlerbehebungs-Unterabschnitte (Job desktop, cache miss, Release-Upload 413) inkl. der realen Fehlerursache aus 18-05 (fehlende clippy-Komponente)"
|
||||||
|
requirement: "DESK-04"
|
||||||
|
verification:
|
||||||
|
- kind: other
|
||||||
|
ref: "CI-Pruefkette aus 18-06-PLAN.md <verify> (cargo-xwin >=2, desktop-dist-, upload-artifact, 'vier Jobs')"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D5
|
||||||
|
description: "Entwicklungshandbuch: apps/desktop nicht mehr als Grundgeruest beschrieben, neuer Abschnitt 'Desktop-App lokal bauen' (Voraussetzungen, desktop-version.sh, lokaler AppImage-Bau, desktop-collect.sh, Hinweis Windows-Installer nur im CI), Testabschnitt um 'vitest run src/desktop' und cargo check/clippy ergaenzt"
|
||||||
|
requirement: "DESK-03"
|
||||||
|
verification:
|
||||||
|
- kind: other
|
||||||
|
ref: "ENTWICKLUNG-Pruefkette aus 18-06-PLAN.md <verify> (0 Treffer 'Tauri-Grundgerüst', Abschnittsueberschrift, desktop-version.sh, vitest run src/desktop)"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D6
|
||||||
|
description: "REQUIREMENTS.md: neuer Abschnitt 'Phase 18 — Desktop-Client fertigstellen' mit DESK-01..05, fuenf Traceability-Zeilen, aktualisierter Coverage-Satz"
|
||||||
|
requirement: "DESK-01"
|
||||||
|
verification:
|
||||||
|
- kind: other
|
||||||
|
ref: "REQ-OK Pruefkette aus 18-06-PLAN.md <verify> (5 DESK-Eintraege, 5 Traceability-Zeilen)"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D7
|
||||||
|
description: "Gesamtlaeufe am Phasenende: API- und Web-Testsuite, beide Typpruefungen, cargo check fuer den Desktop-Client"
|
||||||
|
requirement: "DESK-05"
|
||||||
|
verification:
|
||||||
|
- kind: integration
|
||||||
|
ref: "pnpm --filter @tessera/api exec vitest run (68 Dateien, 1086 Tests, 0 fehlgeschlagen)"
|
||||||
|
status: pass
|
||||||
|
- kind: integration
|
||||||
|
ref: "pnpm --filter @tessera/web exec vitest run (55 Dateien, 365 Tests, 0 fehlgeschlagen)"
|
||||||
|
status: pass
|
||||||
|
- kind: other
|
||||||
|
ref: "pnpm --filter @tessera/api type-check / pnpm --filter @tessera/web type-check (beide fehlerfrei)"
|
||||||
|
status: pass
|
||||||
|
- kind: other
|
||||||
|
ref: "cargo check (apps/desktop/src-tauri) -> Finished"
|
||||||
|
status: pass
|
||||||
|
human_judgment: false
|
||||||
|
- id: D8
|
||||||
|
description: "Bedienprobe des Nutzers auf einem Windows-PC: Download, Installation mit SmartScreen-Anleitung, Erststart mit Server-Adresse, Anmeldung im App-Fenster, Infobereich/Tray-Verhalten, Autostart-Umschaltung, Beenden, Neustart mit gemerkter Adresse, Einstellungsseite Desktop-App"
|
||||||
|
requirement: "DESK-02"
|
||||||
|
verification: []
|
||||||
|
human_judgment: true
|
||||||
|
rationale: "Diese Ausfuehrungsumgebung hat keinen Windows-PC und keine grafische Sitzung — die Bedienprobe (Schritte 1-10 aus dem Plan-<verify>) kann nur der Nutzer selbst auf seinem PC durchfuehren. Dieser Plan liefert Dokumentation und alle automatisierbaren Gesamtlaeufe; die Bedienprobe ist unten unter 'Manuelle Abnahme (ausstehend)' als offener Schritt dokumentiert. DESK-03/04/05 bleiben in REQUIREMENTS.md deshalb bewusst auf Pending, bis das Ergebnis vorliegt."
|
||||||
|
|
||||||
|
duration: 21min
|
||||||
|
completed: 2026-09-16
|
||||||
|
status: complete
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 18 Plan 06: Handbuecher, CHANGELOG, REQUIREMENTS und Gesamtlaeufe zum Abschluss der Desktop-Client-Phase Summary
|
||||||
|
|
||||||
|
**Anwender-, Betriebs- und Entwicklungshandbuch sowie das CI/CD-Runbook beschreiben jetzt vollstaendig die fertige Desktop-App (Download, SmartScreen-Installation, Pipeline-Job, Ablageort im Abbild, Release-Anhaenge, lokaler Bau); CHANGELOG und REQUIREMENTS sind nachgezogen; alle automatisierten Gesamtlaeufe (API 1086 Tests, Web 365 Tests, beide Typpruefungen, cargo check) sind gruen — die Windows-Bedienprobe des Nutzers steht noch aus.**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** 21 min
|
||||||
|
- **Started:** 2026-09-16T15:04:00Z (geschaetzt, erster Lesevorgang der Referenzdateien)
|
||||||
|
- **Completed:** 2026-09-16T15:25:22Z
|
||||||
|
- **Tasks:** 3
|
||||||
|
- **Files modified:** 6
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
- `docs/anleitung-anwender.md`: neues Kapitel „Desktop-App" mit neun Unterabschnitten (Was es ist, Herunterladen, Installation unter Windows inkl. SmartScreen-Anleitung „Trotzdem ausführen", Installation unter Linux, Erster Start mit Server-Adresse, Fenster/Infobereich/Beenden, Automatischer Start, Neue Version, Wenn etwas nicht klappt) samt Inhaltsverzeichnis-Eintrag; nur Platzhalteradressen, keine Firmenadresse im Kapitel.
|
||||||
|
- `CHANGELOG.md`: neuer Stichpunkt „Desktop-App für Windows und Linux: Download auf der Anmeldeseite und unter Einstellungen → Desktop-App" unter „Unveröffentlicht" → „Neu" (D-17, Wortlaut exakt).
|
||||||
|
- `docs/anleitung-betrieb.md`: neues Kapitel 10 „Desktop-App: Pakete und Release-Dateien" (Pipeline-Herkunft, Ablageort `/app/desktop-dist` im API-Abbild samt `manifest.json`, Release-Anhaenge am Gitea-Release, `DESKTOP_DIST_DIR`-Tabelle, Fehlerbilder-Tabelle mit vier Symptomen); Kapitel 9 um einen Verweissatz ergaenzt.
|
||||||
|
- `docs/ci-cd-setup.md`: aus „drei" wurden „vier" Jobs, der Job `desktop` ist jetzt ausfuehrlich beschrieben (Systemabhaengigkeiten, Rust-Toolchain inkl. `clippy`-Komponente, Cache-Reihenfolge vor den Windows-Werkzeugen, Cross-Bau-Reihenfolge AppImage-vor-NSIS, Cache-Schluessel `desktop-dist-{sha}`, Begruendung Cache statt `upload-artifact`); drei neue Fehlerbehebungs-Unterabschnitte („Job desktop schlaegt fehl" inkl. der in 18-05 real aufgetretenen fehlenden `clippy`-Komponente, „publish: cache miss", „Release-Upload 413").
|
||||||
|
- `docs/anleitung-entwicklung.md`: `apps/desktop` wird nicht mehr als Grundgeruest beschrieben, sondern als fertiger Tauri-Client; neuer Abschnitt „Desktop-App lokal bauen" (Voraussetzungen, `desktop-version.sh`, lokaler AppImage-Bau, `desktop-collect.sh`, Hinweis: Windows-Installer nur im CI); Testabschnitt um `pnpm --filter @tessera/api exec vitest run src/desktop` und `cargo check`/`cargo clippy` ergaenzt.
|
||||||
|
- `.planning/REQUIREMENTS.md`: neuer Abschnitt „Phase 18 — Desktop-Client fertigstellen" mit DESK-01..05 (DESK-01/02 aus Phase 6 fortgefuehrt und als Complete markiert, DESK-03..05 neu und auf Pending, bis die Bedienprobe vorliegt), fuenf Traceability-Zeilen, aktualisierter Coverage-Satz.
|
||||||
|
- Gesamtlaeufe am Ende der Phase: `pnpm --filter @tessera/api exec vitest run` — 68 Dateien, **1086 Tests, alle gruen**; `pnpm --filter @tessera/web exec vitest run` — 55 Dateien, **365 Tests, alle gruen**; `pnpm --filter @tessera/api type-check` und `pnpm --filter @tessera/web type-check` — beide fehlerfrei; `cargo check` in `apps/desktop/src-tauri` — `Finished`.
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
Each task was committed atomically:
|
||||||
|
|
||||||
|
1. **Task 1: Anwenderhandbuch — Kapitel "Desktop-App"; CHANGELOG-Stichpunkt** - `43c7061` (docs)
|
||||||
|
2. **Task 2: Betriebshandbuch Kapitel 10, CI/CD-Runbook, Entwicklungshandbuch** - `8f2069b` (docs)
|
||||||
|
3. **Task 3: REQUIREMENTS nachziehen, Gesamtlaeufe, Bedienprobe des Nutzers** - `29219ba` (docs)
|
||||||
|
|
||||||
|
**Plan metadata:** commit pending (this SUMMARY + STATE.md/ROADMAP.md)
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
- `docs/anleitung-anwender.md` - Kapitel „Desktop-App" (9 Unterabschnitte), TOC-Eintrag
|
||||||
|
- `docs/anleitung-betrieb.md` - Kapitel 10, TOC-Eintrag, Verweissatz in Kapitel 9
|
||||||
|
- `docs/anleitung-entwicklung.md` - Monorepo-Beschreibung aktualisiert, Abschnitt „Desktop-App lokal bauen", Testabschnitt ergaenzt
|
||||||
|
- `docs/ci-cd-setup.md` - Job `desktop` beschrieben, drei neue Fehlerbehebungs-Unterabschnitte
|
||||||
|
- `CHANGELOG.md` - Stichpunkt unter Unveröffentlicht/Neu
|
||||||
|
- `.planning/REQUIREMENTS.md` - Abschnitt DESK-01..05, Traceability-Zeilen, Coverage-Satz
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
- DESK-03/04/05 bleiben in REQUIREMENTS.md auf Pending, bis die Bedienprobe des Nutzers (Windows-Installation, siehe unten) tatsaechlich stattgefunden hat — dieser Plan konnte nur die Dokumentation und die automatisierten Gesamtlaeufe liefern, nicht die grafische Bedienprobe (kein Windows-PC/keine grafische Sitzung in dieser Ausfuehrungsumgebung).
|
||||||
|
- In Kapitel 9 des Betriebshandbuchs einen Verweissatz auf das neue Kapitel 10 ergaenzt, statt Kapitel 10 isoliert am Dateiende stehen zu lassen — der Freigabe-Ablauf und die Desktop-Release-Anhaenge gehoeren inhaltlich zusammen.
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None - plan executed exactly as written.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
None.
|
||||||
|
|
||||||
|
## Manuelle Abnahme (ausstehend)
|
||||||
|
|
||||||
|
Die folgende Bedienprobe konnte in dieser Ausfuehrungsumgebung nicht durchgefuehrt werden (kein Windows-PC, keine grafische Sitzung) und steht noch aus. Voraussetzung: der Testserver laeuft auf dem Beta-Stand mit den Paketen (Lauf 367, Commit `742fb5c` — `Tessera-Setup-1.1.0-beta.742fb5c.exe`, `Tessera-1.1.0-beta.742fb5c.AppImage`); `docker compose pull` und `docker compose up -d --force-recreate` fuehrt der Nutzer dort selbst aus.
|
||||||
|
|
||||||
|
1. Anmeldeseite des Testservers im Browser oeffnen: Unter dem Formular steht „Desktop-App herunterladen (Windows)", daneben „Linux-Version", darunter „Version 1.1.0".
|
||||||
|
2. Auf den Windows-Link klicken: Es laedt `Tessera-Setup-1.1.0-beta.{kennung}.exe` (wenige MB).
|
||||||
|
3. Datei ausfuehren. Windows zeigt die SmartScreen-Warnung: „Weitere Informationen" → „Trotzdem ausführen". Die Installation laeuft ohne weitere Fragen durch; Tessera startet (sonst ueber das Startmenue).
|
||||||
|
4. Erststart-Seite: dunkle Karte mit Tessera-Zeichen und gelbem Schriftzug, Feld „Adresse Ihres Tessera-Servers". Adresse des Testservers eintragen (`https://…`), „Verbinden": kurz „Tessera 1.1.0 gefunden – Verbindung wird hergestellt …", dann erscheint die Tessera-Anmeldung **im App-Fenster**.
|
||||||
|
5. Anmelden. Fenster mit X schliessen: Die App bleibt im Infobereich (Symbol mit Tessera-Zeichen). Linksklick auf das Symbol: Fenster ist wieder da.
|
||||||
|
6. Rechtsklick auf das Symbol: Menue „Öffnen", „Update herunterladen" (ausgegraut, weil die aktuelle Version installiert ist), „Mit Windows starten" (ohne Haken), „Beenden" — mit Umlauten.
|
||||||
|
7. „Mit Windows starten" anklicken: Haken erscheint; erneut anklicken: Haken verschwindet.
|
||||||
|
8. „Beenden": App ist weg (auch aus dem Infobereich).
|
||||||
|
9. App erneut starten: Sie geht **direkt** zu Tessera (Adresse gemerkt), Fenstergroesse und -position wie beim Beenden.
|
||||||
|
10. In der App: Einstellungen → Allgemein → „Desktop-App": Seite mit „Aktuelle Version: 1.1.0", „Beta-Ausgabe, Stand {kennung}", zwei gelbe Knoepfe „Für Windows herunterladen" / „Für Linux herunterladen", darunter Dateiname und Groesse (z. B. „… · 101,5 MB" fuer Linux), und vier Saetze Erklaerung.
|
||||||
|
11. Falls ein Linux-Rechner greifbar ist: AppImage herunterladen, ausfuehrbar machen, starten — Erststart-Seite wie unter 4.
|
||||||
|
|
||||||
|
**Nach erfolgreicher Bedienprobe:** DESK-03/04/05 in `.planning/REQUIREMENTS.md` (Requirement-Liste und Traceability-Tabelle) auf Complete setzen.
|
||||||
|
|
||||||
|
### Auf den naechsten Freigabe-Tag vertagt (nicht Teil dieser Phase)
|
||||||
|
|
||||||
|
Zwei Punkte lassen sich erst beim naechsten Freigabe-Tag pruefen und gehoeren in die Abnahme dieser Version, nicht in diese Phase:
|
||||||
|
|
||||||
|
- **(a) Update-Hinweis:** Nach dem Tag `v1.2.0` zeigt der installierte 1.1.0-Client beim Start die Benachrichtigung „Neue Version 1.2.0 verfügbar …", und der Menueeintrag heisst „Version 1.2.0 herunterladen" und oeffnet die Seite Desktop-App im Browser.
|
||||||
|
- **(b) Release-Anhang:** Der Gitea-Release `v1.2.0` traegt `Tessera-Setup-1.2.0.exe` und `Tessera-1.2.0.AppImage` als Dateien.
|
||||||
|
|
||||||
|
## User Setup Required
|
||||||
|
|
||||||
|
Keine externe Dienstkonfiguration noetig. Die Bedienprobe oben ist keine Konfigurationsaufgabe, sondern eine manuelle Verifikation durch den Nutzer.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
- Die Desktop-Client-Phase ist inhaltlich und dokumentarisch abgeschlossen: alle sechs Plaene (18-01 bis 18-06) sind erledigt, alle automatisierten Gesamtlaeufe sind gruen.
|
||||||
|
- Offen bleibt ausschliesslich die manuelle Bedienprobe des Nutzers auf einem Windows-PC (siehe „Manuelle Abnahme (ausstehend)" oben) sowie die zwei auf den naechsten Freigabe-Tag vertagten Punkte (Update-Hinweis, Release-Anhang).
|
||||||
|
- Kein technischer Blocker fuer die naechste Phase oder fuer eine Freigabe — die Bedienprobe ist eine reine Abnahmehandlung, keine offene Implementierungsarbeit.
|
||||||
|
|
||||||
|
---
|
||||||
|
*Phase: 18-desktop-client-fertigstellen*
|
||||||
|
*Completed: 2026-09-16*
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
All modified/created files verified on disk (`docs/anleitung-anwender.md`, `docs/anleitung-betrieb.md`, `docs/anleitung-entwicklung.md`, `docs/ci-cd-setup.md`, `CHANGELOG.md`, `.planning/REQUIREMENTS.md`, this SUMMARY). All three task commits found in `git log` (`43c7061`, `8f2069b`, `29219ba`). All plan-level `<verify>` items re-run and passing: `DOCS1-OK` (Anwenderhandbuch Kapitel/TOC/9 Unterabschnitte/Pflichtbeschriftungen/0 Firmenadressen, CHANGELOG-Position), `DOCS2-OK` (Betriebshandbuch Kapitel 10/TOC/Variable/Ablageort/Fehlerbilder, CI/CD-Runbook vier Jobs/cargo-xwin/Cache-Schluessel/upload-artifact-Begruendung, Entwicklungshandbuch kein Grundgeruest mehr/neuer Abschnitt/Testzeile), `REQ-OK` (5 DESK-Eintraege, 5 Traceability-Zeilen). `ALL-GREEN` bestaetigt: API 1086/1086, Web 365/365, beide Typpruefungen fehlerfrei, `cargo check` `Finished`. Die Bedienprobe (Windows-PC) ist laut Plan-Checkpoint-Protokoll nicht Teil dieses automatisierten Selbst-Checks — siehe „Manuelle Abnahme (ausstehend)" oben.
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
phase: 18-desktop-client-fertigstellen
|
||||||
|
fixed_at: 2026-09-16T17:40:00Z
|
||||||
|
review_path: .planning/phases/18-desktop-client-fertigstellen/18-REVIEW.md
|
||||||
|
iteration: 1
|
||||||
|
findings_in_scope: 4
|
||||||
|
fixed: 4
|
||||||
|
skipped: 2
|
||||||
|
status: all_fixed
|
||||||
|
verification_env: main checkout (workflow.use_worktrees=false, no isolated worktree used)
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 18: Code Review Fix Report
|
||||||
|
|
||||||
|
**Fixed at:** 2026-09-16T17:40:00Z
|
||||||
|
**Source review:** `.planning/phases/18-desktop-client-fertigstellen/18-REVIEW.md`
|
||||||
|
**Iteration:** 1
|
||||||
|
|
||||||
|
**Summary:**
|
||||||
|
- Findings in scope (Critical + Warning): 4
|
||||||
|
- Fixed: 4
|
||||||
|
- Skipped (Info, out of scope by instruction): 2
|
||||||
|
|
||||||
|
Verification ran directly in the main checkout at `/home/vicolab/projects/tessera-ctl` — `.planning/config.json` has `workflow.use_worktrees: false`, so no isolated git worktree was created for this run; per the fixer's setup rules this is the documented, safe opt-out path.
|
||||||
|
|
||||||
|
## Fixed Issues
|
||||||
|
|
||||||
|
### CR-01: Path-traversal defense-in-depth regex accepts dot-only filenames
|
||||||
|
|
||||||
|
**Files modified:** `apps/api/src/desktop/desktop.service.ts`, `apps/api/src/desktop/desktop.service.spec.ts`
|
||||||
|
**Commit:** `0d5c80f`
|
||||||
|
**Applied fix:** In `getPackage()` step (4), `entry.name === '.'` and `entry.name === '..'` are now rejected explicitly (the character-class regex alone accepted them since `.` and `-` are both allowed characters). Additionally, the resolved absolute path is now checked to still start with the resolved `desktopDistDir` before any filesystem access, as a second, independent layer of defense against future variants of this pattern if the character whitelist is ever reused elsewhere. Added `desktop.service.spec.ts` Test 7a (`entry.name: '..'` → 404), Test 7b (`entry.name: '.'` → 404), and Test 7c (`entry.name: '../manifest.json'` → 404, matching the exact case named in the fix task).
|
||||||
|
**Verification:** Tier 1 (re-read, clean) + Tier 2 (`vitest run src/desktop`: 13/13 passing; `tsc --noEmit`: clean).
|
||||||
|
|
||||||
|
### WR-01: Tauri CSP grants `'unsafe-eval'` and wildcard sources that are never needed
|
||||||
|
|
||||||
|
**File modified:** `apps/desktop/src-tauri/tauri.conf.json`
|
||||||
|
**Commit:** `1b2f803`
|
||||||
|
**Applied fix:** Tightened `app.security.csp` from `default-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src *; img-src * data:; font-src * data:; style-src 'self' 'unsafe-inline' *; script-src 'self' 'unsafe-inline' 'unsafe-eval'` to `default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'` — exactly the value REVIEW.md suggested. Confirmed by reading `setup.html`: it uses no `eval()`, no remote fonts/images/styles, and talks to the Rust side only via `window.__TAURI__.core.invoke` (IPC bridge, not `fetch()`). Confirmed via Tauri knowledge that `app.security.csp` is injected only into responses served by the app's own asset protocol (the bundled frontend, i.e. `setup.html`) — the `window.navigate()` call in `save_server_url()` that follows loads the user's configured server fresh, governed by that server's own response headers, not by this config, so tightening `connect-src` here does not affect the subsequently loaded remote page.
|
||||||
|
**Verification:** Tier 1 (re-read, clean) + Tier 2 (`node -e "JSON.parse(...)"`: valid JSON).
|
||||||
|
|
||||||
|
### WR-02: Desktop update check ignores commit/channel — beta users between tags never see "update available"
|
||||||
|
|
||||||
|
**Files modified:** `apps/desktop/src-tauri/build.rs`, `apps/desktop/src-tauri/src/lib.rs`
|
||||||
|
**Commit:** `579e24b`
|
||||||
|
**Applied fix:** `build.rs` now runs `git rev-parse --short=7 HEAD` at compile time and embeds the result as `APP_COMMIT` via `cargo:rustc-env` (falls back to an empty string if `git` is unavailable, e.g. a source tarball without `.git`). This mirrors exactly the format `desktop-collect.sh` already writes into `manifest.json`'s `commit` field. `lib.rs`'s `DesktopLatest` struct now also deserializes `channel` and `commit` (both already present in every `/desktop/latest` response per `DesktopLatestResponse`); the update-available check is now `info.version != app_version || (info.channel == "beta" && info.commit != app_commit)`, so beta clients see the notice for a newer commit on the same tag-derived version, while the live channel keeps the plain version comparison. D-07 (plain `X.Y.Z` in `tauri.conf.json`/`Cargo.toml`, unaffected by this change) is untouched — `desktop-version.sh` was not modified.
|
||||||
|
**Verification:** Tier 1 (re-read, clean) + Tier 2 (`cargo check`: clean; `cargo clippy -- -D warnings`: clean, forced re-run via `touch src/lib.rs`).
|
||||||
|
**Note:** This is a logic-level fix to an async comparison with no existing Rust unit tests in this crate to exercise it automatically (only `cargo check`/`clippy`, which verify syntax/lints, not runtime behavior). Per the fixer's verification policy for logic findings, **this one requires human/manual verification** before relying on it — e.g. building a beta package, bumping only the commit (not the tag-derived version), and confirming the tray notice now appears. Flagged in `18-REVIEW.md`.
|
||||||
|
|
||||||
|
### WR-03: `getManifest()` shallow-validates `files`; malformed entries fall through to string-coerced lookups
|
||||||
|
|
||||||
|
**Files modified:** `apps/api/src/desktop/desktop.service.ts`, `apps/api/src/desktop/desktop.service.spec.ts`
|
||||||
|
**Commit:** `a8964f1`
|
||||||
|
**Applied fix:** `getManifest()`'s top-level shape check now also rejects `files` being an array (`Array.isArray(parsed.files)`, since `typeof [] === 'object'` previously slipped through). A new `isValidManifestFileEntry()` helper validates each present platform entry has `name: string`, `size: number`, and `sha256: string` matching a 64-character hex pattern (`/^[a-f0-9]{64}$/i`) — stricter than REVIEW.md's minimum suggestion (which only asked for the three `typeof` checks), matching the fix-task's explicit scope instruction to also validate the sha256 hex format. A malformed entry makes the whole manifest treated as missing (`null` return, same 404 path, plus a `logger.warn`), matching the docstring's stated guarantee that a bad manifest shape means 404. Added Test 9 (manifest with `name` missing on the `linux` entry → `getLatest()` throws `NotFoundException` instead of proceeding to a stringified-`undefined` lookup) and Test 10 (`sha256: 'not-a-hash'` → download 404).
|
||||||
|
**Verification:** Tier 1 (re-read, clean) + Tier 2 (`vitest run src/desktop`: 13/13 passing; `tsc --noEmit`: clean).
|
||||||
|
|
||||||
|
## Skipped Issues
|
||||||
|
|
||||||
|
### IN-01: Redundant/duplicated version-format validation in `desktop-collect.sh`
|
||||||
|
|
||||||
|
**File:** `.gitea/scripts/desktop-collect.sh:66-77`
|
||||||
|
**Reason:** Info-severity finding, explicitly out of scope for this fix run per the fix task's scope instruction ("Skip the two Info findings (document as skipped)"). No code change made.
|
||||||
|
**Original issue:** The `case` glob pattern is immediately followed by a strict `grep -qE` doing the actual validation; the glob branch adds no protection the grep doesn't already provide.
|
||||||
|
|
||||||
|
### IN-02: `apps/desktop/src-tauri/capabilities/default.json` grants `opener:allow-open-url` for any http/https URL
|
||||||
|
|
||||||
|
**File:** `apps/desktop/src-tauri/capabilities/default.json:17`
|
||||||
|
**Reason:** Info-severity finding, explicitly out of scope for this fix run. The finding itself also states "no action required unless the opener use expands to cover more than the update link" — not a code change candidate even under broader scope.
|
||||||
|
**Original issue:** Capability allows opening any http/https URL in the system browser; currently only used for the tray "Update herunterladen" link to the user-configured server, consistent with D-02 (no fixed built-in server) and not expressible more tightly in the static capability file.
|
||||||
|
|
||||||
|
## Gate Results
|
||||||
|
|
||||||
|
| Gate | Result |
|
||||||
|
|------|--------|
|
||||||
|
| `pnpm --filter @tessera/api exec vitest run src/desktop` | 13/13 passed |
|
||||||
|
| `pnpm --filter @tessera/api type-check` | clean (`tsc --noEmit`, no errors) |
|
||||||
|
| `cargo check` (`apps/desktop/src-tauri`) | clean |
|
||||||
|
| `cargo clippy -- -D warnings` (`apps/desktop/src-tauri`) | clean, no warnings |
|
||||||
|
| `pnpm --filter @tessera/web exec vitest run` | not run — no web files touched by any of the four fixes |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Fixed: 2026-09-16T17:40:00Z_
|
||||||
|
_Fixer: Claude (gsd-code-fixer)_
|
||||||
|
_Iteration: 1_
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
---
|
||||||
|
phase: 18-desktop-client-fertigstellen
|
||||||
|
reviewed: 2026-09-16T00:00:00Z
|
||||||
|
depth: standard
|
||||||
|
files_reviewed: 23
|
||||||
|
files_reviewed_list:
|
||||||
|
- apps/api/src/desktop/desktop.controller.ts
|
||||||
|
- apps/api/src/desktop/desktop.service.ts
|
||||||
|
- apps/api/src/desktop/desktop.module.ts
|
||||||
|
- apps/api/src/desktop/desktop.service.spec.ts
|
||||||
|
- apps/api/src/app.module.ts
|
||||||
|
- apps/api/Dockerfile
|
||||||
|
- apps/desktop/src-tauri/src/lib.rs
|
||||||
|
- apps/desktop/src/setup.html
|
||||||
|
- apps/desktop/src-tauri/capabilities/default.json
|
||||||
|
- apps/desktop/src-tauri/tauri.conf.json
|
||||||
|
- apps/desktop/src-tauri/Cargo.toml
|
||||||
|
- apps/web/src/lib/desktop.ts
|
||||||
|
- apps/web/src/lib/desktop.test.ts
|
||||||
|
- apps/web/src/components/desktop/desktop-download-links.tsx
|
||||||
|
- apps/web/src/components/settings/desktop-app-settings.tsx
|
||||||
|
- apps/web/src/components/settings/settings-sidebar.tsx
|
||||||
|
- apps/web/src/app/(auth)/login/page.tsx
|
||||||
|
- apps/web/src/app/(portal)/settings/general/desktop/page.tsx
|
||||||
|
- .gitea/workflows/ci.yml
|
||||||
|
- .gitea/scripts/desktop-collect.sh
|
||||||
|
- .gitea/scripts/desktop-version.sh
|
||||||
|
- .gitea/scripts/publish-images.sh
|
||||||
|
- .gitea/scripts/publish-release.sh
|
||||||
|
findings:
|
||||||
|
critical: 1
|
||||||
|
warning: 3
|
||||||
|
info: 2
|
||||||
|
total: 6
|
||||||
|
status: clean
|
||||||
|
fixed_at: 2026-09-16T17:40:00Z
|
||||||
|
fix_report: 18-REVIEW-FIX.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 18: Code Review Report
|
||||||
|
|
||||||
|
**Reviewed:** 2026-09-16T00:00:00Z
|
||||||
|
**Depth:** standard
|
||||||
|
**Files Reviewed:** 23
|
||||||
|
**Status:** clean (all Critical/Warning findings fixed — see `18-REVIEW-FIX.md`)
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Reviewed the desktop-client-fertigstellen phase: the new `apps/api/src/desktop/` module (public `latest`/`download` routes), the Tauri client's server-address setup flow (`check_server`/`save_server_url`, tray/update UI in `lib.rs`), the web download surfaces (login page link, Settings → Allgemein → Desktop-App), and the CI/CD pipeline that builds, collects, and publishes the desktop packages (`ci.yml`, `desktop-collect.sh`, `desktop-version.sh`, `publish-images.sh`, `publish-release.sh`).
|
||||||
|
|
||||||
|
Overall the phase is careful about the things it calls out as security-sensitive: the CI scripts build all JSON with `jq -n`/`--arg` (no manual string concatenation), the Gitea release token is only ever passed to curl via a header file (never on the command line or in a URL), temp files holding the token are created under a `umask 077` directory, and the HTTP-facing platform parameter on `GET /desktop/download/:platform` is whitelisted before any filesystem access (verified against real path-traversal-style HTTP requests in `desktop.service.spec.ts` Test 4). The Tauri capability/CSP surface and version-check flow largely match the locked decisions in `18-CONTEXT.md` (D-08 no network dependency, D-09 no signing).
|
||||||
|
|
||||||
|
One genuine gap was found in the second-layer defense against a tampered `manifest.json` (`desktop.service.ts`, D-10/T-18-02): the "defense in depth" filename regex does not reject filenames composed only of dots, so an entry name of `".."` passes the check and `path.join()`s outside `desktop-dist/`. This is not reachable from the public HTTP request today (the manifest is CI-written, not request-controlled), but it is precisely the case the code's own comment says this check exists to block, and it should be fixed to actually do so, especially since the same guard pattern may get reused elsewhere. Three warnings and two info items round out the rest of the findings — none of them break the stated D-10/D-08/D-09 decisions on their own, but they're worth cleaning up.
|
||||||
|
|
||||||
|
## Critical Issues
|
||||||
|
|
||||||
|
### CR-01: Path-traversal defense-in-depth regex accepts dot-only filenames
|
||||||
|
|
||||||
|
**File:** `apps/api/src/desktop/desktop.service.ts:111`
|
||||||
|
**Issue:** Step (4) is documented as "Verteidigung in der Tiefe (T-18-02): auch ein manipuliertes Manifest darf nicht aus dem Ordner hinausfuehren" — the whole point is that even if `manifest.json`'s `files[platform].name` were corrupted/attacker-influenced, the regex should stop it from resolving outside `desktopDistDir`. The regex used is:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
if (!/^[A-Za-z0-9._-]+$/.test(entry.name)) {
|
||||||
|
throw new NotFoundException(`No package for platform: ${knownPlatform}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`.` and `-` are both allowed characters, so a name of exactly `".."` (or `"."`, `"..."`, etc.) passes this test — it contains only characters from the allowed class. Verified directly:
|
||||||
|
```
|
||||||
|
node -e "console.log(/^[A-Za-z0-9._-]+$/.test('..'))" // true
|
||||||
|
node -e "console.log(require('path').join('/app/desktop-dist','..'))" // '/app'
|
||||||
|
```
|
||||||
|
With `entry.name === '..'`, `path.join(this.desktopDistDir, entry.name)` resolves to the *parent* of `desktop-dist/` (e.g. `/app` in the container image). `fs.existsSync('/app')` is `true` (it's a directory), so the code proceeds to `fs.createReadStream('/app')`, which will emit an `EISDIR` stream error rather than serving a file — not a full data-exfiltration primitive by itself, but it is a real escape of the intended containment boundary, defeats the explicitly-documented guarantee, and produces an unhandled stream-error path (headers already sent) instead of the intended 404. The unit test suite (`desktop.service.spec.ts` Test 7) only exercises `"../x.AppImage"` (rejected because of the `/`), not a bare `".."`/`"."`, so this gap has no test coverage either.
|
||||||
|
|
||||||
|
Current exploitability requires `manifest.json` itself to be corrupted or attacker-controlled (today it is written exclusively by `desktop-collect.sh` in CI), so the live attack surface is currently narrow — but the code and the phase's own decision record (D-10, T-18-02) both frame this exact line as the safety net for that scenario, and it doesn't hold.
|
||||||
|
|
||||||
|
**Fix:** Don't rely on a character whitelist alone; verify the resolved path is still inside `desktopDistDir`, and/or explicitly reject `.`/`..` segments:
|
||||||
|
```ts
|
||||||
|
if (
|
||||||
|
!/^[A-Za-z0-9._-]+$/.test(entry.name) ||
|
||||||
|
entry.name === '.' ||
|
||||||
|
entry.name === '..'
|
||||||
|
) {
|
||||||
|
throw new NotFoundException(`No package for platform: ${knownPlatform}`);
|
||||||
|
}
|
||||||
|
const filePath = path.join(this.desktopDistDir, entry.name);
|
||||||
|
const resolvedRoot = path.resolve(this.desktopDistDir) + path.sep;
|
||||||
|
if (!path.resolve(filePath).startsWith(resolvedRoot)) {
|
||||||
|
throw new NotFoundException(`No package for platform: ${knownPlatform}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Add a unit test asserting `entry.name: '..'` and `entry.name: '.'` in the manifest both yield 404 (mirroring the existing Test 7 for `"../x.AppImage"`).
|
||||||
|
|
||||||
|
**Status:** fixed — commit `0d5c80f`. `.`/`..` are now rejected explicitly and the resolved path is additionally checked against `desktopDistDir`. Added Test 7a/7b/7c (`..`, `.`, `../manifest.json`) to `desktop.service.spec.ts`. See `18-REVIEW-FIX.md`.
|
||||||
|
|
||||||
|
## Warnings
|
||||||
|
|
||||||
|
### WR-01: Tauri CSP grants `'unsafe-eval'` and wildcard sources that are never needed
|
||||||
|
|
||||||
|
**File:** `apps/desktop/src-tauri/tauri.conf.json:24`
|
||||||
|
**Issue:** `app.security.csp` is:
|
||||||
|
```json
|
||||||
|
"default-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src *; img-src * data:; font-src * data:; style-src 'self' 'unsafe-inline' *; script-src 'self' 'unsafe-inline' 'unsafe-eval'"
|
||||||
|
```
|
||||||
|
This CSP applies to the app's own bundled page (`apps/desktop/src/setup.html`) — the only local page the app serves. That page is a static, inline `<style>`/`<script type="module">` document that calls `eval()` nowhere, loads no remote fonts/images/styles, and only talks to the Tauri IPC bridge (`window.__TAURI__.core.invoke`). Granting `'unsafe-eval'` and wildcard `connect-src`/`img-src`/`font-src`/`style-src` removes CSP's protection against script injection (e.g. via a future dependency compromise or a bug that echoes untrusted content into the DOM) for no functional benefit — none of the permissive directives are exercised by the current page.
|
||||||
|
**Fix:** Tighten to what `setup.html` actually needs, e.g.:
|
||||||
|
```json
|
||||||
|
"csp": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'"
|
||||||
|
```
|
||||||
|
`connect-src` doesn't need to allow the user-entered server address here because `check_server`/`save_server_url` go through Rust (`reqwest`, `window.navigate`), not `fetch()` from the page itself. If a concrete need for `'unsafe-eval'` or a wildcard source turns up later, add only that directive with a comment explaining why.
|
||||||
|
|
||||||
|
**Status:** fixed — commit `1b2f803`. CSP tightened to exactly the suggested value (`default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'`). Confirmed via Tauri knowledge that this CSP applies only to pages served by the app's own asset protocol (the bundled `setup.html`) — the subsequent `window.navigate()` to the user's server loads a fresh page governed by that server's own headers, not this config. See `18-REVIEW-FIX.md`.
|
||||||
|
|
||||||
|
### WR-02: Desktop update check ignores commit/channel — beta users between tags never see "update available"
|
||||||
|
|
||||||
|
**File:** `apps/desktop/src-tauri/src/lib.rs:17-20, 199-215`
|
||||||
|
**Issue:** The update check compares only the numeric semantic version:
|
||||||
|
```rust
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct DesktopLatest {
|
||||||
|
version: String,
|
||||||
|
}
|
||||||
|
...
|
||||||
|
if info.version != app_version {
|
||||||
|
```
|
||||||
|
`desktop-version.sh` (D-07) sets both `tauri.conf.json`'s `version` and `Cargo.toml`'s `version` from the *last reachable release tag*, not a fresh per-build number — so on `main` (beta channel), every commit between two tags produces a new build/beta package (`Tessera-X.Y.Z-beta.<sha>.AppImage`) whose `env!("CARGO_PKG_VERSION")` and whose freshly-published `manifest.json.version` are numerically identical (both `X.Y.Z` from the same last tag). A user running an older beta build from three commits ago will never be notified that a newer beta package exists, because the only field compared (`version`) hasn't changed — even though `commit`/`channel` in the JSON response did. This directly undermines D-13's stated purpose ("bei abweichender Version Benachrichtigung 'Neue Version X.Y.Z verfuegbar'") for anyone tracking the beta channel between tags.
|
||||||
|
**Fix:** Either compare `commit` as well when `channel == "beta"`, or accept this as an intentional scope limit (only tagged releases trigger the notice) and document it explicitly in `docs/anleitung-anwender.md`/`anleitung-betrieb.md` so it isn't mistaken for a bug later. If fixed in code:
|
||||||
|
```rust
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct DesktopLatest {
|
||||||
|
version: String,
|
||||||
|
channel: String,
|
||||||
|
commit: String,
|
||||||
|
}
|
||||||
|
...
|
||||||
|
let app_commit = option_env!("APP_COMMIT").unwrap_or("");
|
||||||
|
let is_newer = info.version != app_version || (info.channel == "beta" && info.commit != app_commit);
|
||||||
|
```
|
||||||
|
(requires threading a build-time commit stamp into the desktop binary, which doesn't currently exist — flagging as a design gap either way.)
|
||||||
|
|
||||||
|
**Status:** fixed, requires human verification — commit `579e24b`. `build.rs` now embeds `APP_COMMIT` at compile time via `git rev-parse --short=7 HEAD` (same format `desktop-collect.sh` writes to `manifest.json`); `lib.rs` compares `commit` in addition to `version` when `channel == "beta"`, plain version comparison for `live`. D-07 (plain X.Y.Z in `tauri.conf.json`/`Cargo.toml`) untouched. `cargo check` and `cargo clippy -- -D warnings` are clean. No Rust unit tests exist in this crate to exercise the comparison logic automatically (flagged per the fixer's logic-bug verification policy) — recommend a manual check of a beta build before/after a no-version commit to confirm the notice now appears. See `18-REVIEW-FIX.md`.
|
||||||
|
|
||||||
|
### WR-03: `getManifest()` shallow-validates `files`; malformed entries fall through to string-coerced lookups
|
||||||
|
|
||||||
|
**File:** `apps/api/src/desktop/desktop.service.ts:48, 104-113`
|
||||||
|
**Issue:** The manifest shape check only verifies `typeof parsed.files === 'object' && parsed.files !== null`, which also accepts an array (`typeof [] === 'object'`). Separately, individual file entries (`manifest.files[platform]`) are never checked for having the required `name`/`size`/`sha256` string/number fields before being used — e.g. if `entry.name` were `undefined` (malformed manifest), `/^[A-Za-z0-9._-]+$/.test(undefined)` coerces to the string `"undefined"`, which matches the regex and proceeds to look for a literal file called `undefined` in `desktop-dist/`. This doesn't currently produce an exploitable outcome (ends in 404), but it's a symptom of `getManifest()` trusting more of the JSON shape than its own docstring claims ("die Grundform nicht stimmt ... 404"), and it means a broken manifest doesn't fail loudly/clearly for whoever is debugging a bad CI run.
|
||||||
|
**Fix:** Validate each present platform entry has `typeof entry.name === 'string' && typeof entry.size === 'number' && typeof entry.sha256 === 'string'` inside `getManifest()`, logging and returning `null` (same pattern already used for the top-level shape check) if not.
|
||||||
|
|
||||||
|
**Status:** fixed — commit `a8964f1`. `getManifest()` now also rejects `files` being an array, and validates each present platform entry (`name`: string, `size`: number, `sha256`: 64-char hex string) via a new `isValidManifestFileEntry()` helper; a malformed entry makes the whole manifest treated as missing (404 + warn log), matching the docstring's stated guarantee. Added Test 9 (missing `name`) and Test 10 (non-hex `sha256`) to `desktop.service.spec.ts`. See `18-REVIEW-FIX.md`.
|
||||||
|
|
||||||
|
## Info
|
||||||
|
|
||||||
|
### IN-01: Redundant/duplicated version-format validation in `desktop-collect.sh`
|
||||||
|
|
||||||
|
**File:** `.gitea/scripts/desktop-collect.sh:66-77`
|
||||||
|
**Issue:** The `case` pattern (`[0-9]*.[0-9]*.[0-9]*`) is a loose glob check that's immediately followed by a strict `grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'` doing the actual validation — the outer `case` only decides whether to run the `grep`, but the `*)` fallback arm duplicates the exact same error message and exit. The glob branch adds no protection the grep doesn't already provide on its own.
|
||||||
|
**Fix:** Collapse to a single check:
|
||||||
|
```sh
|
||||||
|
if ! printf '%s' "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
|
||||||
|
echo "Version '$VERSION' aus $TAURI_DIR/tauri.conf.json ist nicht rein numerisch (X.Y.Z)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
**Status:** skipped — Info item, out of scope for this fix run (scope limited to the Critical and Warning findings). No code change made.
|
||||||
|
|
||||||
|
### IN-02: `apps/desktop/src-tauri/capabilities/default.json` grants `opener:allow-open-url` for any http/https URL
|
||||||
|
|
||||||
|
**File:** `apps/desktop/src-tauri/capabilities/default.json:17`
|
||||||
|
**Issue:** `{ "identifier": "opener:allow-open-url", "allow": [{ "url": "https://*" }, { "url": "http://*" }] }` lets the Rust side open *any* http/https URL in the system browser — currently only used for the tray "Update herunterladen" item, which opens `{stored server}/settings/general/desktop`. Since the stored server address is arbitrary user input (by design, D-02: no fixed built-in server), this is consistent with the product's multi-tenant intent and isn't a capability-scoping bug per se, but it's broader than strictly necessary (a same-origin-as-configured-server restriction isn't expressible in the static capability file, so this is effectively as tight as it can be made without runtime scoping). Noting for awareness only — no action required unless the opener use expands to cover more than the update link.
|
||||||
|
|
||||||
|
**Status:** skipped — Info item, out of scope for this fix run (scope limited to the Critical and Warning findings). No action required per the finding itself.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Reviewed: 2026-09-16T00:00:00Z_
|
||||||
|
_Reviewer: Claude (gsd-code-reviewer)_
|
||||||
|
_Depth: standard_
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
status: testing
|
||||||
|
phase: 18-desktop-client-fertigstellen
|
||||||
|
source: [18-VERIFICATION.md]
|
||||||
|
started: 2026-09-16T18:15:00Z
|
||||||
|
updated: 2026-09-16T18:15:00Z
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Test
|
||||||
|
|
||||||
|
number: 1
|
||||||
|
name: Windows-Bedienprobe (Download, Installation, Erststart, Tray, Einstellungsseite)
|
||||||
|
expected: |
|
||||||
|
Beta-Server auf dem Stand mit den Paketen aus dem CI-Lauf nach Push von `72e488e` (oder neuer).
|
||||||
|
1. Anmeldeseite zeigt unter dem Formular "Desktop-App herunterladen (Windows)" mit Versionsangabe.
|
||||||
|
2. Klick laedt `Tessera-Setup-1.1.0-beta.<commit>.exe` (ca. 2,7 MB).
|
||||||
|
3. Installation unter Windows: SmartScreen-Hinweis erscheint ("Weitere Informationen" -> "Trotzdem ausfuehren"), danach installiert der Installer ohne weitere Nachfrage.
|
||||||
|
4. Erststart: Fenster in Tessera-Gestalt fragt nach der Server-Adresse; falsche Adresse -> Fehlermeldung; richtige Adresse -> Tessera-Anmeldung im App-Fenster.
|
||||||
|
5. Anmeldung funktioniert, Dashboard erscheint im App-Fenster.
|
||||||
|
6. Fenster schliessen (X) -> App bleibt im Infobereich (Tray). Rechtsklick auf das Symbol: "Oeffnen", "Update herunterladen" (ggf. ausgegraut/fehlend ohne neue Version), "Mit Windows starten" (Haken), "Beenden".
|
||||||
|
7. "Mit Windows starten" anhaken -> nach Ab-/Anmelden von Windows startet Tessera im Tray.
|
||||||
|
8. "Beenden" beendet die App vollstaendig.
|
||||||
|
9. Neustart der App: Server-Adresse ist gemerkt, direkt Anmeldung/Dashboard.
|
||||||
|
10. Einstellungen -> Allgemein -> Desktop-App zeigt Version, beide Download-Knoepfe mit Dateigroesse und den Erklaerungstext.
|
||||||
|
11. (optional, Linux) `Tessera-1.1.0-beta.<commit>.AppImage` ausfuehrbar machen und starten -> gleiche Erststart-Seite.
|
||||||
|
awaiting: user response
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
### 1. Windows-Bedienprobe (Download, Installation, Erststart, Tray, Einstellungsseite)
|
||||||
|
expected: siehe oben (Schritte 1-11)
|
||||||
|
result: [pending]
|
||||||
|
|
||||||
|
### 2. Release-Anhang am naechsten Freigabe-Tag
|
||||||
|
expected: Nach dem naechsten Tag `vX.Y.Z` traegt der Gitea-Release `Tessera-Setup-X.Y.Z.exe` und `Tessera-X.Y.Z.AppImage` als Anhaenge (herunterladbar, Groesse > 0). Pruefbar erst bei der naechsten Freigabe (z. B. 1.2.0).
|
||||||
|
result: [pending]
|
||||||
|
|
||||||
|
### 3. Update-Hinweis bei neuerer Client-Version
|
||||||
|
expected: Ein aelterer installierter Client zeigt nach dem Start die Benachrichtigung "Neue Version X.Y.Z verfuegbar" und im Tray den Eintrag "Update herunterladen", der die Seite Einstellungen -> Desktop-App im Browser oeffnet. Auf der Beta reicht dafuer ein neuerer Commit (gleiche Versionsnummer, anderer Commit-Stempel); auf Live der naechste Freigabe-Tag.
|
||||||
|
result: [pending]
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
total: 3
|
||||||
|
passed: 0
|
||||||
|
issues: 0
|
||||||
|
pending: 3
|
||||||
|
skipped: 0
|
||||||
|
blocked: 0
|
||||||
|
|
||||||
|
## Gaps
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
---
|
||||||
|
phase: 18-desktop-client-fertigstellen
|
||||||
|
verified: 2026-09-16T17:50:00Z
|
||||||
|
status: human_needed
|
||||||
|
score: 8/10 must-haves verified
|
||||||
|
covered_files: [".gitea/scripts/desktop-collect.sh", ".gitea/scripts/desktop-version.sh", ".gitea/scripts/publish-images.sh", ".gitea/scripts/publish-release.sh", ".gitea/workflows/ci.yml", ".planning/REQUIREMENTS.md", ".planning/phases/18-desktop-client-fertigstellen/18-01-PLAN.md", ".planning/phases/18-desktop-client-fertigstellen/18-01-SUMMARY.md", ".planning/phases/18-desktop-client-fertigstellen/18-02-PLAN.md", ".planning/phases/18-desktop-client-fertigstellen/18-02-SUMMARY.md", ".planning/phases/18-desktop-client-fertigstellen/18-03-PLAN.md", ".planning/phases/18-desktop-client-fertigstellen/18-03-SUMMARY.md", ".planning/phases/18-desktop-client-fertigstellen/18-04-PLAN.md", ".planning/phases/18-desktop-client-fertigstellen/18-04-SUMMARY.md", ".planning/phases/18-desktop-client-fertigstellen/18-05-PLAN.md", ".planning/phases/18-desktop-client-fertigstellen/18-05-SUMMARY.md", ".planning/phases/18-desktop-client-fertigstellen/18-06-PLAN.md", ".planning/phases/18-desktop-client-fertigstellen/18-06-SUMMARY.md", ".planning/phases/18-desktop-client-fertigstellen/18-REVIEW-FIX.md", ".planning/phases/18-desktop-client-fertigstellen/18-REVIEW.md", "CHANGELOG.md", "apps/api/src/desktop/desktop.controller.ts", "apps/api/src/desktop/desktop.module.ts", "apps/api/src/desktop/desktop.service.ts", "apps/desktop/src-tauri/build.rs", "apps/desktop/src-tauri/src/lib.rs", "apps/desktop/src/setup.html", "apps/web/src/app/(portal)/settings/general/desktop/page.tsx", "apps/web/src/components/desktop/desktop-download-links.tsx", "apps/web/src/components/settings/desktop-app-settings.tsx", "apps/web/src/lib/desktop.ts", "docs/anleitung-anwender.md", "docs/anleitung-betrieb.md", "docs/anleitung-entwicklung.md", "docs/ci-cd-setup.md"]
|
||||||
|
covered_digest: "v1:sha256:d489cb5b094aba560b96f3d6ce7537d67fe83a2827906d554c4cccae996e091c"
|
||||||
|
behavior_unverified: 2
|
||||||
|
behavior_unverified_items:
|
||||||
|
- truth: "Ein Freigabe-Tag (v*) baut beide Pakete und haengt sie als Dateien an den Gitea-Release (D-01, D-04..D-08, ROADMAP-SC1 zweite Haelfte)."
|
||||||
|
test: "Naechsten Freigabe-Tag (z.B. v1.2.0) setzen und pushen; Job desktop + publish beobachten, danach den Gitea-Release des Tags oeffnen."
|
||||||
|
expected: "Der Release traegt Tessera-Setup-1.2.0.exe und Tessera-1.2.0.AppImage als Anhaenge (Groesse > 0, herunterladbar)."
|
||||||
|
why_human: "publish-release.sh laeuft laut Workflow-Bedingung nur bei einem echten v*-Tag-Push; Lauf 367 war ein main-Push (kein Tag), der Release-Schritt lief dort erwartungsgemaess nicht. Das Skript selbst ist per --dry-run und Unit-Ebene geprueft (18-02), aber der echte GET/DELETE/POST-Roundtrip gegen die Gitea-Release-API mit einer ~100 MB-Datei ist nur am echten Tag beobachtbar."
|
||||||
|
- truth: "Der installierte Client zeigt nach Adresseingabe die Tessera-Anmeldung, behaelt Tray/Schliessen-ins-Tray/Autostart aus Phase 6 bei und weist bei einer neueren Client-Version per Benachrichtigung + Tray-Link auf die neue Version hin (ROADMAP-SC3)."
|
||||||
|
test: "Bedienprobe aus 18-06-SUMMARY.md 'Manuelle Abnahme (ausstehend)', Schritte 1-11: Download vom Testserver, Windows-Installation inkl. SmartScreen, Erststart mit Server-Adresse, Anmeldung im App-Fenster, Tray-Verhalten (Oeffnen/Update/Autostart-Haken/Beenden), Neustart mit gemerkter Adresse, Einstellungsseite."
|
||||||
|
expected: "Alle 11 Schritte laufen wie in der Bedienprobe beschrieben; nach einem spaeteren Freigabe-Tag zeigt ein aelterer Client zusaetzlich die Update-Benachrichtigung mit Download-Link."
|
||||||
|
why_human: "Diese Ausfuehrungsumgebung ist kopflos (kein Windows-PC, kein Display/X11/Wayland). Alle unterstuetzenden Schichten sind automatisiert bewiesen (cargo check/clippy sauber, grep-Batterien fuer Tray-Text/Umlaute/Versionspruefungs-Code, WR-02-Fix fuer den Commit-Vergleich verifiziert per Code-Lesen), aber das tatsaechliche Rendering/Verhalten in einer grafischen Sitzung ist nicht pruefbar."
|
||||||
|
overrides_applied: 0
|
||||||
|
human_verification:
|
||||||
|
- test: "Naechster Freigabe-Tag: Release-Anhang pruefen (siehe behavior_unverified_items #1)"
|
||||||
|
expected: "Beide Dateien am Gitea-Release des Tags vorhanden"
|
||||||
|
why_human: "publish-release.sh laeuft nur bei Tag-Push; kein Tag in diesem Verifizierungslauf gesetzt"
|
||||||
|
- test: "Windows-Bedienprobe des Nutzers (siehe behavior_unverified_items #2, 18-06-SUMMARY.md Schritte 1-11)"
|
||||||
|
expected: "Installer laeuft, Erststart-Seite fuehrt zur Anmeldung, Tray/Autostart/Beenden funktionieren, Einstellungsseite zeigt Version/Knoepfe"
|
||||||
|
why_human: "Kein Windows-PC/keine grafische Sitzung in dieser Ausfuehrungsumgebung; DESK-03/04/05 bleiben laut REQUIREMENTS.md bewusst auf Pending bis diese Probe erfolgt ist"
|
||||||
|
- test: "Beta-Update-Hinweis zwischen zwei Tags (WR-02-Fix, commit-basierter Vergleich)"
|
||||||
|
expected: "Ein aelterer Beta-Client (gleiche X.Y.Z, aelterer Commit) zeigt nach einem neuen Beta-Build die Benachrichtigung"
|
||||||
|
why_human: "Keine Rust-Unit-Tests in diesem Crate fuer die Vergleichslogik; REVIEW-FIX.md flaggt dies explizit als 'requires human/manual verification'"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 18: Desktop-Client fertigstellen Verification Report
|
||||||
|
|
||||||
|
**Phase Goal:** Anwender koennen den Tessera-Desktop-Client als fertigen Windows-Installer (und Linux-AppImage) direkt aus Tessera herunterladen und installieren; die Pipeline baut die Pakete bei jedem Freigabe-Tag und haengt sie an das Gitea-Release; der Client traegt die Freigabe-Version, fragt die Server-Adresse weiterhin beim ersten Start ab und weist bei einer neueren Client-Version mit Download-Link hin.
|
||||||
|
|
||||||
|
**Verified:** 2026-09-16T17:50:00Z
|
||||||
|
**Status:** human_needed
|
||||||
|
**Re-verification:** No — initial verification
|
||||||
|
|
||||||
|
## Goal Achievement
|
||||||
|
|
||||||
|
### Observable Truths
|
||||||
|
|
||||||
|
| # | Truth | Status | Evidence |
|
||||||
|
|---|-------|--------|----------|
|
||||||
|
| 1 | `GET /desktop/latest` (200 mit Version/Kanal/Dateiliste, 404 ohne Manifest) und `GET /desktop/download/:platform` (attachment-Stream, Whitelist vor Dateisystemzugriff, 400 fuer unbekannte Plattform) sind oeffentlich (`@Public()`) | ✓ VERIFIED | `pnpm --filter @tessera/api exec vitest run src/desktop` — 13/13 gruen (inkl. CR-01/WR-03-Regressionstests); lokaler curl: `GET /desktop/latest` → 200 mit Manifest, `GET /desktop/download/linux` → 200 mit `Content-Disposition: attachment`, `GET /desktop/download/nonsense` → 400 |
|
||||||
|
| 2 | `desktop-collect.sh`/`desktop-version.sh` schreiben kanonische Dateinamen, `manifest.json` (Version/Kanal/Commit/Groesse/SHA-256) und reine `X.Y.Z`-Versionen aus dem Freigabe-Tag | ✓ VERIFIED | Skripte vorhanden (156/53 Zeilen), von CI-Lauf 367 tatsaechlich benutzt (siehe Truth 4); Code-Review fand keine Beanstandung außer IN-01 (Info, nicht behoben, kein Sicherheitsproblem) |
|
||||||
|
| 3 | API-Abbild traegt die Pakete unter `/app/desktop-dist/` und liefert sie aus, ohne dass Live-Server Gitea-Zugang brauchen (D-08) | ✓ VERIFIED | `apps/api/Dockerfile` enthaelt `COPY desktop-dist`; lokaler Docker-Stack liefert `/desktop/latest` und `/desktop/download/linux` tatsaechlich aus (curl-Beweis oben; Hinweis: das laufende lokale Abbild ist ein aelterer Baustand, siehe Anmerkung unten) |
|
||||||
|
| 4 | CI-Job `desktop` baut auf dem Linux-Runner sowohl `Tessera-X.Y.Z.AppImage` als auch `Tessera-Setup-X.Y.Z.exe` (Cross-Bau, cargo-xwin/NSIS) und uebergibt sie per `actions/cache` an `publish`, das ohne Manifest hart abbricht | ✓ VERIFIED | Gitea CI/CD Lauf 367 (Commit `742fb5c`), Job "Desktop-Pakete bauen" gruen, erzeugte `Tessera-Setup-1.1.0-beta.742fb5c.exe` (2.775.663 Bytes) und `Tessera-1.1.0-beta.742fb5c.AppImage` (82.479.608 Bytes); `.gitea/workflows/ci.yml` enthaelt `cargo-xwin`, `--require linux,windows`, `fail-on-cache-miss: true`, `needs: desktop`; `publish-images.sh` bricht ohne `desktop-dist/manifest.json` hart ab (Code-Inspektion, Zeile 63-64) |
|
||||||
|
| 5 | `publish` haengt Pakete ins API-/Web-Abbild; Job `publish` von Lauf 367 pushte Abbilder mit den Paketen | ✓ VERIFIED | 18-05-SUMMARY.md: Lauf 367 — alle vier Jobs gruen, `publish` pushte Abbilder mit Etiketten `beta`/`latest` |
|
||||||
|
| 6 | `publish-release.sh` haengt bei Tags jede Manifest-Datei idempotent (GET/DELETE/POST) als Release-Anhang an; Token verlaesst nie die Kommandozeile | ✓ VERIFIED (Code + Trockenlauf) | `.gitea/scripts/publish-release.sh` enthaelt `upload_asset()`, `HDR_AUTH` (nur `Authorization`-Header), kein `GITEA_TOKEN` in einer `curl`-Zeile (grep bestaetigt); `--dry-run --tag v1.1.0` nennt laut 18-02-SUMMARY.md den erwarteten Zielpfad — der echte Upload bei einem Tag ist aber noch nicht gelaufen (siehe Truth 7) |
|
||||||
|
| 7 | Ein Freigabe-Tag haengt beide Dateien tatsaechlich als Release-Anhang an den Gitea-Release (ROADMAP-SC1, zweite Haelfte) | ⚠️ PRESENT_BEHAVIOR_UNVERIFIED | Code/Skript vorhanden und per Trockenlauf geprueft, aber kein echter `v*`-Tag wurde seit den Phase-18-Aenderungen gepusht (Lauf 367 war ein `main`-Push) — der reale API-Roundtrip ist unbewiesen. Siehe `behavior_unverified_items` |
|
||||||
|
| 8 | Anmeldeseite zeigt einen unauffaelligen "Desktop-App herunterladen (Windows)"-Link mit Linux-Kurzlink und Version nur wenn `/desktop/latest` antwortet; Einstellungen → Allgemein → Desktop-App zeigt Version, zwei Download-Knoepfe, Dateigroesse, Erklaerung; alle Downloads laufen ueber `API_URL` (ROADMAP-SC2) | ✓ VERIFIED | `apps/web/src/app/(auth)/login/page.tsx` importiert/rendert `DesktopDownloadLinks`; `settings-sidebar.tsx` verlinkt `/settings/general/desktop`; `pnpm --filter @tessera/web exec vitest run src/lib/desktop.test.ts src/components/desktop src/components/settings/desktop-app-settings.test.tsx` — 11/11 gruen; volle Web-Suite 365/365 gruen; `desktopDownloadUrl()` baut Adressen ausschliesslich aus `API_URL` + relativem `url`-Feld |
|
||||||
|
| 9 | Client fragt die Server-Adresse beim ersten Start ab, prueft sie echt (`check_server`/`/health/version`), speichert sie, navigiert zur Tessera-Anmeldung; Tray behaelt Oeffnen/Schliessen-ins-Tray/Autostart aus Phase 6 und traegt zusaetzlich einen Update-Eintrag mit echten Umlauten; Versionspruefung gegen `/desktop/latest` inkl. Commit-Vergleich fuer Beta (WR-02-Fix) | ⚠️ PRESENT_BEHAVIOR_UNVERIFIED | Code vollstaendig vorhanden und verdrahtet: `check_server`/`save_server_url`-Kommandos, `api_url()`-Helfer, Tray-Eintraege "Öffnen"/"Update herunterladen"/Autostart-Haken/"Beenden" mit korrektem Label je Plattform, `is_newer`-Vergleich inkl. `channel == "beta" && info.commit != app_commit` (WR-02, `build.rs` embeds `APP_COMMIT` via `TESSERA_COMMIT`-Env aus der Pipeline); `cargo check`/`cargo clippy` sauber. Das tatsaechliche Verhalten in einer grafischen Sitzung (echtes Rendering, echter Serverwechsel, echte Benachrichtigung) ist in dieser kopflosen Umgebung nicht beobachtbar — Bedienprobe steht laut 18-06-SUMMARY.md aus. Siehe `behavior_unverified_items` |
|
||||||
|
| 10 | Anwender-, Betriebs- und Entwicklungshandbuch beschreiben Installation, Erststart, Tray, Pipeline, Release-Dateien und Umgebungsvariablen (ROADMAP-SC4) | ✓ VERIFIED | `docs/anleitung-anwender.md` Kapitel `## Desktop-App` (Zeile 161) mit den geforderten Unterabschnitten; `docs/anleitung-betrieb.md` Kapitel `## 10. Desktop-App: Pakete und Release-Dateien` (Zeile 552) mit `DESKTOP_DIST_DIR`; `docs/anleitung-entwicklung.md` enthaelt `### Desktop-App lokal bauen` und keinen Treffer mehr fuer "Tauri-Grundgerüst"; `docs/ci-cd-setup.md` beschreibt den Job `desktop`; `CHANGELOG.md` traegt den D-17-Stichpunkt |
|
||||||
|
|
||||||
|
**Score:** 8/10 truths verified (2 present, behavior-unverified)
|
||||||
|
|
||||||
|
### Anmerkung zum lokalen Docker-Stack
|
||||||
|
|
||||||
|
Der laufende lokale API-Container liefert `/desktop/latest` mit `"channel":"dev","commit":"ae8fecb"` — das ist ein aelterer, lokal gebauter Stand aus 18-01, nicht der aktuelle Code mit den Review-Fixes (CR-01/WR-01/WR-02/WR-03). Dieser Befund bestaetigt nur, dass die Route/das Streaming-Verhalten funktioniert (Truth 1/3) — er ist **keine** Evidenz dafuer, dass die Review-Fixes in einem laufenden Abbild aktiv sind. Die Review-Fixes selbst sind stattdessen ueber den frisch ausgefuehrten `vitest run src/desktop` (13/13, inkl. der neuen Test 7a/7b/7c und Test 9/10) sowie `cargo check`/`cargo clippy` bewiesen, wie vom Auftraggeber vorgegeben.
|
||||||
|
|
||||||
|
### Required Artifacts
|
||||||
|
|
||||||
|
| Artifact | Expected | Status | Details |
|
||||||
|
|----------|----------|--------|---------|
|
||||||
|
| `apps/api/src/desktop/desktop.service.ts` | Manifest lesen, Plattform-Whitelist, Datei-Stream, Pfad-Traversal-Schutz | ✓ VERIFIED | 176 Zeilen; `PLATFORMS`-Whitelist vor Dateisystemzugriff; CR-01-Fix (`.`/`..`-Ablehnung + `startsWith(resolvedRoot)`) und WR-03-Fix (`isValidManifestFileEntry`) beide im Code vorhanden und getestet |
|
||||||
|
| `apps/api/src/desktop/desktop.controller.ts` | `GET /desktop/latest`, `GET /desktop/download/:platform`, beide `@Public()` | ✓ VERIFIED | 37 Zeilen, `@Inject(DesktopService)` explizit gesetzt (Vitest/esbuild-Workaround) |
|
||||||
|
| `.gitea/scripts/desktop-collect.sh` / `desktop-version.sh` | Pakete einsammeln, Version aus Tag | ✓ VERIFIED | 156/53 Zeilen, in CI-Lauf 367 tatsaechlich benutzt |
|
||||||
|
| `apps/api/Dockerfile` | `COPY desktop-dist` | ✓ VERIFIED | `COPY desktop-dist ./desktop-dist` vor `USER nestjs` (18-01-SUMMARY.md, curl-Beweis bestaetigt Auslieferung) |
|
||||||
|
| `.gitea/workflows/ci.yml` | Job `desktop`, Windows-Cross-Bau, Cache-Uebergabe an `publish` | ✓ VERIFIED | `cargo-xwin`, `--target x86_64-pc-windows-msvc --bundles nsis`, `--require linux,windows`, `TESSERA_COMMIT`-Env, `fail-on-cache-miss: true`, `needs: desktop` alle vorhanden |
|
||||||
|
| `.gitea/scripts/publish-release.sh` | Idempotenter Release-Datei-Upload | ✓ VERIFIED (Code) / ⚠️ Realer Upload nicht beobachtet | `upload_asset()`, `HDR_AUTH`, kein Token in `curl`-Zeile |
|
||||||
|
| `apps/web/src/lib/desktop.ts` + Komponenten | Fetch-Helfer, Login-Link, Einstellungsseite | ✓ VERIFIED | 67/66/122 Zeilen, verdrahtet in `login/page.tsx` und `settings-sidebar.tsx`, 11/11 Tests gruen |
|
||||||
|
| `apps/desktop/src-tauri/src/lib.rs` + `build.rs` | Versionspruefung, Tray, Erststart-Kommandos, Commit-Stempel | ✓ VERIFIED (Code) / ⚠️ Kein grafischer Beweis | 246/35 Zeilen, `cargo check`/`clippy` sauber, WR-02-Fix verdrahtet |
|
||||||
|
| `apps/desktop/src-tauri/icons/*` | Echtes Tessera-Icon statt Platzhalter | ✓ VERIFIED | 5 Dateien vorhanden (icon.ico 105.724 Bytes, icon.png 33.721 Bytes — keine 105-Byte-Platzhalter mehr) |
|
||||||
|
| `docs/anleitung-anwender.md`, `docs/anleitung-betrieb.md`, `docs/anleitung-entwicklung.md`, `docs/ci-cd-setup.md`, `CHANGELOG.md` | Handbuecher + Changelog | ✓ VERIFIED | Alle geforderten Kapitel-Anker gefunden |
|
||||||
|
| `.planning/REQUIREMENTS.md` | DESK-01..05 mit Traceability | ✓ VERIFIED | 5/5 Eintraege, 5/5 Traceability-Zeilen, DESK-01/02 Complete, DESK-03/04/05 bewusst Pending bis Bedienprobe |
|
||||||
|
|
||||||
|
### Key Link Verification
|
||||||
|
|
||||||
|
| From | To | Via | Status | Details |
|
||||||
|
|------|-----|-----|--------|---------|
|
||||||
|
| `.gitea/scripts/desktop-collect.sh` | `apps/api/src/desktop/desktop.service.ts` | `manifest.json` | ✓ WIRED | Manifest-Form stimmt mit `DesktopManifest`-Typ und Service-Lesecode ueberein; curl-Beweis bestaetigt reales Ausliefern |
|
||||||
|
| `apps/api/Dockerfile` | `apps/api/src/desktop/desktop.service.ts` | `COPY desktop-dist` | ✓ WIRED | `desktopDistDir` zeigt auf `/app/desktop-dist`, curl liefert reale Datei |
|
||||||
|
| `.gitea/workflows/ci.yml (desktop)` | `.gitea/workflows/ci.yml (publish)` | `actions/cache` Schluessel `desktop-dist-${{ gitea.sha }}` | ✓ WIRED | Bestaetigt durch gruenen Lauf 367 (alle vier Jobs gruen) |
|
||||||
|
| `apps/web/src/lib/desktop.ts` | `apps/api/src/desktop/desktop.controller.ts` | `fetch(`${API_URL}/desktop/latest`)` | ✓ WIRED | `loadDesktopLatest()` ruft `/desktop/latest`; Unit-Tests decken Erfolg/Fehler ab |
|
||||||
|
| `apps/web/src/components/settings/settings-sidebar.tsx` | `apps/web/src/app/(portal)/settings/general/desktop/page.tsx` | Link `href=/settings/general/desktop` | ✓ WIRED | grep bestaetigt genau 1 Treffer, `aria-current` analog "Konto" |
|
||||||
|
| `apps/desktop/src-tauri/src/lib.rs (Tray "update")` | `apps/web/.../settings/general/desktop/page.tsx` | `opener().open_url({server}/settings/general/desktop)` | ✓ WIRED | Zeile 153 in `lib.rs` baut exakt diese URL |
|
||||||
|
| `.gitea/scripts/publish-release.sh` | `desktop-dist/manifest.json` | `jq -r '.files[].name'` | ✓ WIRED (Code) | Upload-Schleife iteriert Manifest-Dateien; realer Netzaufruf am naechsten Tag noch offen |
|
||||||
|
|
||||||
|
### Behavioral Spot-Checks
|
||||||
|
|
||||||
|
| Behavior | Command | Result | Status |
|
||||||
|
|----------|---------|--------|--------|
|
||||||
|
| API liefert Manifest oeffentlich | `curl -s localhost:3001/desktop/latest` | 200, Manifest mit `files.linux` | ✓ PASS |
|
||||||
|
| API streamt Datei mit attachment-Header | `curl -sI localhost:3001/desktop/download/linux` | 200, `Content-Disposition: attachment` | ✓ PASS |
|
||||||
|
| Unbekannte Plattform vor Dateisystemzugriff abgewiesen | `curl -s localhost:3001/desktop/download/nonsense` | 400 "Unknown platform" | ✓ PASS |
|
||||||
|
| API-Modul-Tests (inkl. Review-Fix-Regressionen) | `pnpm --filter @tessera/api exec vitest run src/desktop` | 13/13 gruen | ✓ PASS |
|
||||||
|
| Web-Suite komplett | `pnpm --filter @tessera/web exec vitest run` | 365/365 gruen | ✓ PASS |
|
||||||
|
| Desktop-spezifische Web-Tests | `pnpm --filter @tessera/web exec vitest run src/lib/desktop.test.ts src/components/desktop src/components/settings/desktop-app-settings.test.tsx` | 11/11 gruen | ✓ PASS |
|
||||||
|
| API Typpruefung | `pnpm --filter @tessera/api exec tsc --noEmit` | fehlerfrei | ✓ PASS |
|
||||||
|
| Web Typpruefung | `pnpm --filter @tessera/web exec tsc --noEmit` | fehlerfrei | ✓ PASS |
|
||||||
|
| Rust-Client kompiliert | `cargo check` (apps/desktop/src-tauri) | `Finished` | ✓ PASS |
|
||||||
|
| Windows-Cross-Bau real in CI | — | Gitea Lauf 367 (bereits vom Auftraggeber gemessen, nicht erneut ausgefuehrt) | ✓ PASS (uebernommene Evidenz) |
|
||||||
|
|
||||||
|
### Requirements Coverage
|
||||||
|
|
||||||
|
| Requirement | Source Plan(s) | Description | Status | Evidence |
|
||||||
|
|-------------|-----------------|--------------|--------|----------|
|
||||||
|
| DESK-01 | 18-01, 18-02, 18-04, 18-05, 18-06 | Tauri-Wrapper Windows+Linux (Phase 6, fortgefuehrt) + Pipeline | ✓ SATISFIED | Code+CI-Lauf 367, REQUIREMENTS.md Complete |
|
||||||
|
| DESK-02 | 18-04, 18-06 | Server-Adresse beim ersten Start (Phase 6, fortgefuehrt) | ✓ SATISFIED (Code) / Bedienprobe offen | `check_server`/`save_server_url` verdrahtet; REQUIREMENTS.md fuehrt DESK-02 als Complete (aus Phase 6, unveraendert) |
|
||||||
|
| DESK-03 | 18-01, 18-03, 18-06 | Installer in Tessera herunterladbar | ✓ SATISFIED (Code+Tests) / REQUIREMENTS.md bewusst Pending | Login-Link/Einstellungsseite verdrahtet und getestet; Statuswechsel auf Complete an Bedienprobe geknuepft |
|
||||||
|
| DESK-04 | 18-02, 18-05, 18-06 | Freigabe-Tag baut beide Pakete, haengt sie an den Release | ⚠️ TEILWEISE | Bau-Haelfte bewiesen (Lauf 367); Release-Anhang-Haelfte nur per Trockenlauf, kein echter Tag in diesem Zyklus |
|
||||||
|
| DESK-05 | 18-01, 18-04, 18-06 | Client traegt Freigabe-Version, Update-Hinweis mit Link | ✓ SATISFIED (Code) / Bedienprobe offen | Versionspruefung inkl. WR-02-Commit-Vergleich verdrahtet, `cargo check`/`clippy` sauber; grafischer Beweis aussteht |
|
||||||
|
|
||||||
|
**Keine verwaisten Requirements.** REQUIREMENTS.md bildet alle 5 DESK-Eintraege korrekt auf Phase 18 ab (DESK-01/02 aus Phase 6 fortgefuehrt); keine zusaetzliche Phase-18-Zuordnung fehlt.
|
||||||
|
|
||||||
|
### Anti-Patterns Found
|
||||||
|
|
||||||
|
Keine. `grep` auf `TODO|FIXME|XXX|TBD|HACK|PLACEHOLDER|not yet implemented|coming soon` in allen 18 neuen/geaenderten Kerndateien (API-Modul, Skripte, Web-Komponenten, Rust-Client, CI-Workflow) ergab 0 Treffer.
|
||||||
|
|
||||||
|
### Code-Review-Status
|
||||||
|
|
||||||
|
`18-REVIEW.md`: 1 Critical (CR-01, Pfad-Traversal-Verteidigung), 3 Warnings (WR-01 CSP, WR-02 Beta-Update-Vergleich, WR-03 Manifest-Validierung), 2 Info (beide bewusst uebersprungen, keine Sicherheitswirkung). Alle 4 Critical/Warning-Funde sind laut `18-REVIEW-FIX.md` behoben und per Commit nachgewiesen (`a8964f1`, `0d5c80f`, `1b2f803`, `579e24b`) — durch eigenes Code-Lesen und `vitest`/`cargo`-Laeufe in diesem Verifizierungslauf bestaetigt. Ein fuenfter, in REVIEW-FIX.md nicht dokumentierter Nachfolge-Commit (`72e488e`) behebt einen Cache-bedingten Folgefehler des WR-02-Fixes (Commit-Stempel wuerde mit warmem Cargo-Cache veraltet bleiben) — inhaltlich konsistent und ebenfalls durch Code-Lesen bestaetigt.
|
||||||
|
|
||||||
|
### Human Verification Required
|
||||||
|
|
||||||
|
1. **Release-Anhang am naechsten Freigabe-Tag**
|
||||||
|
- **Test:** Naechsten `v*`-Tag setzen/pushen, Job `desktop`+`publish` beobachten, danach den Gitea-Release des Tags oeffnen.
|
||||||
|
- **Expected:** `Tessera-Setup-X.Y.Z.exe` und `Tessera-X.Y.Z.AppImage` sind als Anhaenge vorhanden und herunterladbar.
|
||||||
|
- **Why human:** `publish-release.sh` laeuft nur bei einem echten Tag-Push; in diesem Verifizierungszyklus (Lauf 367) war kein Tag gesetzt. Nur per Trockenlauf geprueft.
|
||||||
|
|
||||||
|
2. **Windows-Bedienprobe (Erststart, Tray, Anmeldung, Einstellungsseite)**
|
||||||
|
- **Test:** 18-06-SUMMARY.md "Manuelle Abnahme (ausstehend)", Schritte 1-11.
|
||||||
|
- **Expected:** Installation mit SmartScreen-Hinweis, Erststart fuehrt zur Tessera-Anmeldung im App-Fenster, Tray (Oeffnen/Update/Autostart-Haken/Beenden) funktioniert, Einstellungsseite zeigt Version/Knoepfe/Groesse.
|
||||||
|
- **Why human:** Diese Ausfuehrungsumgebung hat keinen Windows-PC und keine grafische Sitzung.
|
||||||
|
|
||||||
|
3. **Beta-Update-Hinweis zwischen zwei Commits (WR-02-Fix)**
|
||||||
|
- **Test:** Zwei Beta-Builds ohne neuen Tag (nur neuer Commit) — aelterer Client soll die Benachrichtigung zeigen.
|
||||||
|
- **Expected:** Benachrichtigung "Neue Version X.Y.Z verfuegbar" erscheint trotz gleicher `X.Y.Z`-Versionsnummer, weil sich der Commit-Stempel unterscheidet.
|
||||||
|
- **Why human:** Keine Rust-Unit-Tests fuer diese Vergleichslogik; `18-REVIEW-FIX.md` flaggt dies explizit als manuell zu pruefen.
|
||||||
|
|
||||||
|
### Gaps Summary
|
||||||
|
|
||||||
|
Keine blockierenden Luecken gefunden. Der gesamte Code-Pfad (API-Modul, CI-Pipeline inkl. Windows-Cross-Bau, Web-Oberflaeche, Client-Versionspruefung/Tray, Handbuecher, REQUIREMENTS-Traceability) ist vorhanden, verdrahtet und — soweit in dieser kopflosen Umgebung moeglich — automatisiert bewiesen (API 13/13 + volle Web-Suite 365/365, beide Typpruefungen sauber, `cargo check`/`clippy` sauber, Gitea-Lauf 367 gruen mit beiden Paketdateien). Zwei Aspekte des Phasenziels sind bewusst nur bis zur Code-/Trockenlauf-Ebene bewiesen und brauchen eine echte Beobachtung: (a) der Release-Datei-Anhang, der nur bei einem echten Freigabe-Tag auslöst, und (b) die grafische Bedienprobe des Windows-Clients selbst. Beides ist von den Autoren der Phase (SUMMARY 18-06, VALIDATION.md "Manual-Only Verifications") bereits explizit als offen dokumentiert und deckt sich mit der vom Auftraggeber vorgegebenen Erwartung ("expected to be human_needed/deferred, not failures"). REQUIREMENTS.md haelt DESK-03/04/05 konsequent auf Pending, bis diese Proben abgeschlossen sind — das ist korrekt und kein Gap.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Verified: 2026-09-16T17:50:00Z_
|
||||||
|
_Verifier: Claude (gsd-verifier)_
|
||||||
@@ -94,5 +94,5 @@
|
|||||||
"label": "Advance to the next step (verify)",
|
"label": "Advance to the next step (verify)",
|
||||||
"reason": "Phase 18 of 18 · ready to verify"
|
"reason": "Phase 18 of 18 · ready to verify"
|
||||||
},
|
},
|
||||||
"updated_at": "2026-09-16T14:43:23.832Z"
|
"updated_at": "2026-09-16T15:27:01.839Z"
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -6,7 +6,8 @@ Diese Liste beschreibt in einfachen Worten, was sich von Version zu Version an T
|
|||||||
|
|
||||||
### Neu
|
### Neu
|
||||||
|
|
||||||
- Kalender-Widget: Monatsübersicht mit Terminanzahl je Tag, Termine beim Überfahren, darunter „Nächste Termine“
|
- Desktop-App für Windows und Linux: Download auf der Anmeldeseite und unter Einstellungen → Desktop-App
|
||||||
|
- Kalender-Widget: Monatsübersicht mit Terminanzahl je Tag, Termine beim Überfahren, darunter „Nächste Termine”
|
||||||
- Kalender-Widget: Einstellungen für Monatsansicht, Anzahl und Zeitraum der Termine
|
- Kalender-Widget: Einstellungen für Monatsansicht, Anzahl und Zeitraum der Termine
|
||||||
- Favoriten-Widget: optionaler Titel (ohne Titel keine Kopfzeile)
|
- Favoriten-Widget: optionaler Titel (ohne Titel keine Kopfzeile)
|
||||||
|
|
||||||
|
|||||||
@@ -161,7 +161,57 @@ describe('DesktopService/DesktopController — HTTP-Durchstich (Phase 18)', () =
|
|||||||
expect(res.status).toBe(404);
|
expect(res.status).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Test 8 (bewusst oeffentlich): getLatest und download tragen @Public()', () => {
|
it('Test 7a (dot-only Name im Manifest, CR-01): ".." endet mit 404', async () => {
|
||||||
|
writeManifest({
|
||||||
|
linux: { name: '..', size: 123, sha256: 'a'.repeat(64) },
|
||||||
|
});
|
||||||
|
const res = await fetch(`${baseUrl}/desktop/download/linux`);
|
||||||
|
expect(res.status).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test 7b (dot-only Name im Manifest, CR-01): "." endet mit 404', async () => {
|
||||||
|
writeManifest({
|
||||||
|
linux: { name: '.', size: 123, sha256: 'a'.repeat(64) },
|
||||||
|
});
|
||||||
|
const res = await fetch(`${baseUrl}/desktop/download/linux`);
|
||||||
|
expect(res.status).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test 7c (Name mit Traversal-Segment ausserhalb desktopDistDir, CR-01): "../manifest.json" endet mit 404', async () => {
|
||||||
|
writeManifest({
|
||||||
|
linux: { name: '../manifest.json', size: 123, sha256: 'a'.repeat(64) },
|
||||||
|
});
|
||||||
|
const res = await fetch(`${baseUrl}/desktop/download/linux`);
|
||||||
|
expect(res.status).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test 9 (WR-03, ungueltiger Eintrag im Manifest): name fehlt -- getLatest wirft NotFoundException statt "undefined" als Datei zu suchen', () => {
|
||||||
|
// Bewusst am `writeManifest()`-Helper vorbei direkt geschrieben -- dessen
|
||||||
|
// Parametertyp verlangt `name`, hier soll aber genau dessen Fehlen
|
||||||
|
// geprueft werden (kaputtes Manifest, kein TS-Typfehler im Test).
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(tempDir, 'manifest.json'),
|
||||||
|
JSON.stringify({
|
||||||
|
version: '1.1.0',
|
||||||
|
channel: 'dev',
|
||||||
|
commit: 'abc1234',
|
||||||
|
buildTime: '2026-09-16T00:00:00Z',
|
||||||
|
files: { linux: { size: 123, sha256: 'a'.repeat(64) } },
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const service = new DesktopService();
|
||||||
|
expect(() => service.getLatest()).toThrow(NotFoundException);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test 10 (WR-03, ungueltiger Eintrag im Manifest): sha256 ist kein 64-stelliger Hex-String -- 404', async () => {
|
||||||
|
writeManifest({
|
||||||
|
linux: { name: PACKAGE_NAME, size: packageSize, sha256: 'not-a-hash' },
|
||||||
|
});
|
||||||
|
const res = await fetch(`${baseUrl}/desktop/download/linux`);
|
||||||
|
expect(res.status).toBe(404);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Test 11 (bewusst oeffentlich): getLatest und download tragen @Public()', () => {
|
||||||
expect(Reflect.getMetadata(IS_PUBLIC_KEY, DesktopController.prototype.getLatest)).toBe(true);
|
expect(Reflect.getMetadata(IS_PUBLIC_KEY, DesktopController.prototype.getLatest)).toBe(true);
|
||||||
expect(Reflect.getMetadata(IS_PUBLIC_KEY, DesktopController.prototype.download)).toBe(true);
|
expect(Reflect.getMetadata(IS_PUBLIC_KEY, DesktopController.prototype.download)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,6 +15,30 @@ import * as path from 'path';
|
|||||||
*/
|
*/
|
||||||
const PLATFORMS = ['windows', 'linux'] as const;
|
const PLATFORMS = ['windows', 'linux'] as const;
|
||||||
|
|
||||||
|
/** sha256 als Hex-String -- genau 64 Zeichen, 0-9/a-f (Gross-/Kleinschreibung egal). */
|
||||||
|
const SHA256_HEX_RE = /^[a-f0-9]{64}$/i;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Grundform eines einzelnen Datei-Eintrags im Manifest (WR-03, Code-Review
|
||||||
|
* Phase 18): `getManifest()` prueft bislang nur die Kopf-Form, nicht die
|
||||||
|
* einzelnen Plattform-Eintraege -- ein kaputter/unvollstaendiger Eintrag
|
||||||
|
* wuerde sonst unbemerkt bis in `getPackage()` durchgereicht (z. B.
|
||||||
|
* `entry.name === undefined`, das sich zu `"undefined"` coerct und dort
|
||||||
|
* fehlleitend als Dateiname gesucht wird).
|
||||||
|
*/
|
||||||
|
function isValidManifestFileEntry(entry: unknown): entry is DesktopManifestFile {
|
||||||
|
if (typeof entry !== 'object' || entry === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const candidate = entry as Record<string, unknown>;
|
||||||
|
return (
|
||||||
|
typeof candidate.name === 'string' &&
|
||||||
|
typeof candidate.size === 'number' &&
|
||||||
|
typeof candidate.sha256 === 'string' &&
|
||||||
|
SHA256_HEX_RE.test(candidate.sha256)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DesktopService {
|
export class DesktopService {
|
||||||
private readonly logger = new Logger(DesktopService.name);
|
private readonly logger = new Logger(DesktopService.name);
|
||||||
@@ -45,10 +69,24 @@ export class DesktopService {
|
|||||||
try {
|
try {
|
||||||
const raw = fs.readFileSync(manifestPath, 'utf-8');
|
const raw = fs.readFileSync(manifestPath, 'utf-8');
|
||||||
const parsed = JSON.parse(raw) as DesktopManifest;
|
const parsed = JSON.parse(raw) as DesktopManifest;
|
||||||
if (typeof parsed.version !== 'string' || typeof parsed.files !== 'object' || parsed.files === null) {
|
if (
|
||||||
|
typeof parsed.version !== 'string' ||
|
||||||
|
typeof parsed.files !== 'object' ||
|
||||||
|
parsed.files === null ||
|
||||||
|
Array.isArray(parsed.files)
|
||||||
|
) {
|
||||||
this.logger.warn(`manifest.json unter ${manifestPath} hat unerwartete Form`);
|
this.logger.warn(`manifest.json unter ${manifestPath} hat unerwartete Form`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
for (const platform of PLATFORMS) {
|
||||||
|
const entry = parsed.files[platform];
|
||||||
|
if (entry !== undefined && !isValidManifestFileEntry(entry)) {
|
||||||
|
this.logger.warn(
|
||||||
|
`manifest.json unter ${manifestPath} hat einen ungueltigen Eintrag fuer Plattform ${platform}`,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
return parsed;
|
return parsed;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.warn(`manifest.json unter ${manifestPath} konnte nicht gelesen werden: ${error}`);
|
this.logger.warn(`manifest.json unter ${manifestPath} konnte nicht gelesen werden: ${error}`);
|
||||||
@@ -107,13 +145,26 @@ export class DesktopService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// (4) Verteidigung in der Tiefe (T-18-02): auch ein manipuliertes
|
// (4) Verteidigung in der Tiefe (T-18-02): auch ein manipuliertes
|
||||||
// Manifest darf nicht aus dem Ordner hinausfuehren.
|
// Manifest darf nicht aus dem Ordner hinausfuehren. Die Zeichen-Whitelist
|
||||||
if (!/^[A-Za-z0-9._-]+$/.test(entry.name)) {
|
// allein reicht nicht -- "." und ".." bestehen ausschliesslich aus
|
||||||
|
// erlaubten Zeichen, meinen im Dateisystem aber "aktueller"/"uebergeordneter
|
||||||
|
// Ordner". Deshalb zusaetzlich explizit ausschliessen UND den aufgeloesten
|
||||||
|
// Pfad gegen den Zielordner pruefen (haelt auch kuenftige Varianten dieses
|
||||||
|
// Musters ab, falls die Zeichen-Whitelist anderswo wiederverwendet wird).
|
||||||
|
if (
|
||||||
|
!/^[A-Za-z0-9._-]+$/.test(entry.name) ||
|
||||||
|
entry.name === '.' ||
|
||||||
|
entry.name === '..'
|
||||||
|
) {
|
||||||
|
throw new NotFoundException(`No package for platform: ${knownPlatform}`);
|
||||||
|
}
|
||||||
|
const filePath = path.join(this.desktopDistDir, entry.name);
|
||||||
|
const resolvedRoot = path.resolve(this.desktopDistDir) + path.sep;
|
||||||
|
if (!path.resolve(filePath).startsWith(resolvedRoot)) {
|
||||||
throw new NotFoundException(`No package for platform: ${knownPlatform}`);
|
throw new NotFoundException(`No package for platform: ${knownPlatform}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// (5) Datei muss existieren.
|
// (5) Datei muss existieren.
|
||||||
const filePath = path.join(this.desktopDistDir, entry.name);
|
|
||||||
if (!fs.existsSync(filePath)) {
|
if (!fs.existsSync(filePath)) {
|
||||||
throw new NotFoundException(`Package file missing: ${entry.name}`);
|
throw new NotFoundException(`Package file missing: ${entry.name}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,35 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
|
// Commit-Stempel zur Kompilierzeit einbetten (WR-02, Code-Review Phase 18):
|
||||||
|
// `desktop-collect.sh` vergibt fuer den Beta-Kanal (main) jedem Commit
|
||||||
|
// dieselbe X.Y.Z-Version (D-07) -- die Unterscheidung zwischen zwei
|
||||||
|
// Beta-Bauten steckt nur im `commit`-Feld von manifest.json. Ohne einen
|
||||||
|
// eigenen Commit-Stempel im Binary kann `lib.rs` diesen Fall nicht
|
||||||
|
// erkennen. `--short=7` spiegelt exakt das Format, das
|
||||||
|
// `desktop-collect.sh` fuer manifest.json schreibt. Fehlt `git` (z. B.
|
||||||
|
// Quell-Tarball ohne .git-Ordner), bleibt der Wert leer -- dann greift
|
||||||
|
// nur noch der reine Versionsvergleich.
|
||||||
|
//
|
||||||
|
// Vorrang hat die Umgebungsvariable TESSERA_COMMIT (die Pipeline setzt sie
|
||||||
|
// auf den vollen Commit-Hash): der Cargo-Zwischenspeicher wuerde ein
|
||||||
|
// `git rev-parse` sonst nicht neu auswerten, und ein alter Stempel im
|
||||||
|
// Programm liesse den Beta-Update-Hinweis dauerhaft erscheinen.
|
||||||
|
println!("cargo:rerun-if-env-changed=TESSERA_COMMIT");
|
||||||
|
println!("cargo:rerun-if-changed=../../../.git/HEAD");
|
||||||
|
let commit = std::env::var("TESSERA_COMMIT")
|
||||||
|
.ok()
|
||||||
|
.map(|s| s.trim().chars().take(7).collect::<String>())
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.or_else(|| {
|
||||||
|
std::process::Command::new("git")
|
||||||
|
.args(["rev-parse", "--short=7", "HEAD"])
|
||||||
|
.output()
|
||||||
|
.ok()
|
||||||
|
.filter(|output| output.status.success())
|
||||||
|
.and_then(|output| String::from_utf8(output.stdout).ok())
|
||||||
|
.map(|s| s.trim().to_string())
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
println!("cargo:rustc-env=APP_COMMIT={commit}");
|
||||||
|
|
||||||
tauri_build::build()
|
tauri_build::build()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ struct VersionResponse {
|
|||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
struct DesktopLatest {
|
struct DesktopLatest {
|
||||||
version: String,
|
version: String,
|
||||||
|
channel: String,
|
||||||
|
commit: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Baut die Adresse eines API-Pfads aus der gespeicherten Server-Adresse.
|
/// Baut die Adresse eines API-Pfads aus der gespeicherten Server-Adresse.
|
||||||
@@ -193,12 +195,20 @@ pub fn run() {
|
|||||||
if let Some(server_url) = url_for_check {
|
if let Some(server_url) = url_for_check {
|
||||||
let app_handle = app.handle().clone();
|
let app_handle = app.handle().clone();
|
||||||
let app_version = env!("CARGO_PKG_VERSION").to_string();
|
let app_version = env!("CARGO_PKG_VERSION").to_string();
|
||||||
|
let app_commit = env!("APP_COMMIT").to_string();
|
||||||
let update_item = update.clone();
|
let update_item = update.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let url = api_url(&server_url, "/desktop/latest");
|
let url = api_url(&server_url, "/desktop/latest");
|
||||||
if let Ok(resp) = reqwest::get(&url).await {
|
if let Ok(resp) = reqwest::get(&url).await {
|
||||||
if let Ok(info) = resp.json::<DesktopLatest>().await {
|
if let Ok(info) = resp.json::<DesktopLatest>().await {
|
||||||
if info.version != app_version {
|
// Beta-Kanal (main) vergibt jedem Commit dieselbe X.Y.Z-Version
|
||||||
|
// (D-07, desktop-collect.sh) -- ohne den Commit-Vergleich saehe
|
||||||
|
// ein Beta-Client zwischen zwei Freigabe-Tags nie einen neueren
|
||||||
|
// Bau (WR-02, Code-Review Phase 18). Fuer den Live-Kanal bleibt
|
||||||
|
// es beim reinen Versionsvergleich.
|
||||||
|
let is_newer = info.version != app_version
|
||||||
|
|| (info.channel == "beta" && info.commit != app_commit);
|
||||||
|
if is_newer {
|
||||||
let _ = app_handle
|
let _ = app_handle
|
||||||
.notification()
|
.notification()
|
||||||
.builder()
|
.builder()
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
"csp": "default-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src *; img-src * data:; font-src * data:; style-src 'self' 'unsafe-inline' *; script-src 'self' 'unsafe-inline' 'unsafe-eval'"
|
"csp": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ Diese Anleitung richtet sich an alle Kolleginnen und Kollegen, die Tessera im Ar
|
|||||||
- [Zertifikat-Manager](#zertifikat-manager)
|
- [Zertifikat-Manager](#zertifikat-manager)
|
||||||
- [Domaincheck](#domaincheck)
|
- [Domaincheck](#domaincheck)
|
||||||
7. [Persönliche Einstellungen](#persönliche-einstellungen)
|
7. [Persönliche Einstellungen](#persönliche-einstellungen)
|
||||||
8. [Einen Fehler melden](#einen-fehler-melden)
|
8. [Desktop-App](#desktop-app)
|
||||||
9. [Was ist neu](#was-ist-neu)
|
9. [Einen Fehler melden](#einen-fehler-melden)
|
||||||
10. [Häufige Stolpersteine](#häufige-stolpersteine)
|
10. [Was ist neu](#was-ist-neu)
|
||||||
|
11. [Häufige Stolpersteine](#häufige-stolpersteine)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -157,6 +158,58 @@ Ein einfaches Werkzeug, um zu prüfen, ob eine Internet-Domain verfügbar ist. G
|
|||||||
|
|
||||||
**Sprache:** Unten in der Seitenleiste finden Sie die Sprachumschaltung zwischen Deutsch und Englisch.
|
**Sprache:** Unten in der Seitenleiste finden Sie die Sprachumschaltung zwischen Deutsch und Englisch.
|
||||||
|
|
||||||
|
## Desktop-App
|
||||||
|
|
||||||
|
Tessera steht zusätzlich als eigenständige App für Windows und Linux zur Verfügung — für alle, die Tessera lieber in einem eigenen Fenster statt im Browser-Tab nutzen möchten.
|
||||||
|
|
||||||
|
### Was die Desktop-App ist
|
||||||
|
|
||||||
|
Die Desktop-App öffnet Tessera in einem eigenen Fenster statt in einem Browser-Tab. Nach dem Start legt sie ein Symbol im Infobereich Ihrer Taskleiste ab und bietet dieselben Funktionen wie die gewohnte Tessera-Oberfläche im Browser — es ist derselbe Tessera-Server, nur mit einem eigenen Fenster.
|
||||||
|
|
||||||
|
### Herunterladen
|
||||||
|
|
||||||
|
Sie finden die Desktop-App an zwei Stellen, beide ohne Zugang zu Gitea:
|
||||||
|
|
||||||
|
- Auf der **Anmeldeseite** unterhalb des Formulars: „Desktop-App herunterladen (Windows)" und daneben „Linux-Version".
|
||||||
|
- Angemeldet unter **Einstellungen → Allgemein → Desktop-App**: dort sehen Sie zusätzlich die aktuelle Version, den Dateinamen und die Dateigröße.
|
||||||
|
|
||||||
|
### Installation unter Windows
|
||||||
|
|
||||||
|
Führen Sie die heruntergeladene Datei `Tessera-Setup-X.Y.Z.exe` aus. Windows zeigt dabei die Meldung „Der Computer wurde durch Windows geschützt": Klicken Sie auf „Weitere Informationen" und danach auf „Trotzdem ausführen". Der Grund dafür: Die App ist für den internen Gebrauch nicht signiert, das Paket stammt aber unmittelbar aus Ihrem eigenen Tessera-Server. Danach erscheint „Tessera" im Startmenü. Eine neuere Version installieren Sie einfach über die bestehende — die bereits eingetragene Server-Adresse bleibt dabei erhalten.
|
||||||
|
|
||||||
|
### Installation unter Linux
|
||||||
|
|
||||||
|
Machen Sie die Datei `Tessera-X.Y.Z.AppImage` ausführbar (über die Dateieigenschaften oder mit `chmod +x`) und starten Sie sie anschließend. Eine Installation ist dafür nicht nötig.
|
||||||
|
|
||||||
|
### Erster Start: Server-Adresse
|
||||||
|
|
||||||
|
Beim ersten Start fragt die App nach der Adresse, unter der Sie Tessera auch im Browser öffnen (zum Beispiel `https://tessera.example.com`). Die App prüft die Adresse und meldet „Tessera X.Y.Z gefunden". Bei einer `http`-Adresse erscheint zusätzlich ein Hinweis — die Verbindung ist trotzdem möglich. Danach folgt die gewohnte Tessera-Anmeldung, direkt im App-Fenster.
|
||||||
|
|
||||||
|
### Fenster, Infobereich und Beenden
|
||||||
|
|
||||||
|
Schließen Sie das Fenster über das X, legt sich Tessera lediglich in den Infobereich Ihrer Taskleiste, statt sich zu beenden. Ein Linksklick auf das Symbol dort öffnet das Fenster wieder. Ein Rechtsklick zeigt ein Menü mit:
|
||||||
|
|
||||||
|
- **Öffnen**
|
||||||
|
- **Update herunterladen**
|
||||||
|
- **Mit Windows starten** (unter Linux: **Beim Anmelden starten**) — mit Häkchen
|
||||||
|
- **Beenden**
|
||||||
|
|
||||||
|
Nur „Beenden" beendet die App tatsächlich, auch im Infobereich. Fenstergröße und -position merkt sich die App bis zum nächsten Start.
|
||||||
|
|
||||||
|
### Automatischer Start
|
||||||
|
|
||||||
|
Setzen oder entfernen Sie das Häkchen bei „Mit Windows starten" (bzw. „Beim Anmelden starten") im Rechtsklick-Menü des Infobereich-Symbols. Ab Werk ist der automatische Start ausgeschaltet.
|
||||||
|
|
||||||
|
### Neue Version
|
||||||
|
|
||||||
|
Ist eine neuere Version verfügbar, meldet sich die App beim Start mit „Neue Version X.Y.Z verfügbar". Der Menüeintrag „Update herunterladen" heißt dann „Version X.Y.Z herunterladen" und öffnet mit einem Klick die Seite Einstellungen → Desktop-App im Browser — dort laden Sie die neue Version herunter und installieren sie wie oben beschrieben. Ein automatisches Aktualisieren gibt es nicht.
|
||||||
|
|
||||||
|
### Wenn etwas nicht klappt
|
||||||
|
|
||||||
|
- **„Unter dieser Adresse antwortet kein Tessera-Server"** — prüfen Sie die eingegebene Adresse; gemeint ist die Adresse, unter der Sie Tessera im Browser öffnen, nicht eine interne API-Adresse.
|
||||||
|
- **Der Download-Link fehlt auf der Anmeldeseite** — der Server trägt derzeit keine Desktop-Pakete. Fragen Sie in diesem Fall Ihren Administrator.
|
||||||
|
- **Windows zeigt die SmartScreen-Warnung** — das ist normal und erwartet, siehe [Installation unter Windows](#installation-unter-windows).
|
||||||
|
|
||||||
## Einen Fehler melden
|
## Einen Fehler melden
|
||||||
|
|
||||||
Wenn etwas in Tessera nicht so funktioniert, wie Sie es erwarten, müssen Sie niemandem lange erklären, was Sie gesehen haben: Klicken Sie oben rechts in der Kopfleiste auf den Knopf **Fehler melden** (Käfer-Symbol). Tessera nimmt sofort ein Bild der aktuellen Seite auf — genau das, was Sie gerade sehen — und öffnet danach ein kleines Fenster mit einer Vorschau dieses Bildes.
|
Wenn etwas in Tessera nicht so funktioniert, wie Sie es erwarten, müssen Sie niemandem lange erklären, was Sie gesehen haben: Klicken Sie oben rechts in der Kopfleiste auf den Knopf **Fehler melden** (Käfer-Symbol). Tessera nimmt sofort ein Bild der aktuellen Seite auf — genau das, was Sie gerade sehen — und öffnet danach ein kleines Fenster mit einer Vorschau dieses Bildes.
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ Betrieb der bereits laufenden Installation, nicht deren automatisierten Build.
|
|||||||
7. [Protokolle und Fehlersuche](#7-protokolle-und-fehlersuche)
|
7. [Protokolle und Fehlersuche](#7-protokolle-und-fehlersuche)
|
||||||
8. [Abgrenzung zur CI/CD-Pipeline](#8-abgrenzung-zur-cicd-pipeline)
|
8. [Abgrenzung zur CI/CD-Pipeline](#8-abgrenzung-zur-cicd-pipeline)
|
||||||
9. [Zwei Kanäle: Live und Beta](#9-zwei-kanäle-live-und-beta)
|
9. [Zwei Kanäle: Live und Beta](#9-zwei-kanäle-live-und-beta)
|
||||||
|
10. [Desktop-App: Pakete und Release-Dateien](#10-desktop-app-pakete-und-release-dateien)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -450,9 +451,13 @@ weigert, ist eine frühere Korrektur (siehe Hotfix, Schritt 5) noch nicht zurüc
|
|||||||
Zweig `live` wird nur geprüft, der Tag `vX.Y.Z` wird gebaut und als `live` und
|
Zweig `live` wird nur geprüft, der Tag `vX.Y.Z` wird gebaut und als `live` und
|
||||||
`vX.Y.Z` abgelegt. Das dauert etwa vier bis sechs Minuten.
|
`vX.Y.Z` abgelegt. Das dauert etwa vier bis sechs Minuten.
|
||||||
|
|
||||||
|
Derselbe Tag baut zusätzlich die Desktop-Pakete (Windows-Installer und
|
||||||
|
Linux-AppImage) und hängt beide als Dateien an denselben Release an – Details
|
||||||
|
dazu in [Kapitel 10](#10-desktop-app-pakete-und-release-dateien).
|
||||||
|
|
||||||
Beim Tag legt die Pipeline zusätzlich einen **Release in Gitea** an: Name
|
Beim Tag legt die Pipeline zusätzlich einen **Release in Gitea** an: Name
|
||||||
„Tessera X.Y.Z“, Text ist der Abschnitt dieser Version aus `CHANGELOG.md`. Sie
|
„Tessera X.Y.Z”, Text ist der Abschnitt dieser Version aus `CHANGELOG.md`. Sie
|
||||||
finden ihn im Repository unter „Releases“. Fehlt der Abschnitt in der
|
finden ihn im Repository unter „Releases”. Fehlt der Abschnitt in der
|
||||||
Änderungsliste, schlägt genau dieser letzte Schritt fehl – die Abbilder sind dann
|
Änderungsliste, schlägt genau dieser letzte Schritt fehl – die Abbilder sind dann
|
||||||
trotzdem gebaut und abgelegt. Der Release wird nachgeholt, sobald der Abschnitt
|
trotzdem gebaut und abgelegt. Der Release wird nachgeholt, sobald der Abschnitt
|
||||||
nachgetragen ist: entweder durch erneutes Auslösen des Tag-Laufs oder lokal per
|
nachgetragen ist: entweder durch erneutes Auslösen des Tag-Laufs oder lokal per
|
||||||
@@ -543,3 +548,70 @@ Kapitel 2 gilt vollständig. Die Abweichungen gegenüber alpha:
|
|||||||
dieses Etikett noch nicht – deshalb erst freigeben, dann installieren. Für einen
|
dieses Etikett noch nicht – deshalb erst freigeben, dann installieren. Für einen
|
||||||
Probelauf davor kann vorübergehend `IMAGE_TAG=beta` stehen; danach auf `live`
|
Probelauf davor kann vorübergehend `IMAGE_TAG=beta` stehen; danach auf `live`
|
||||||
umstellen und `pull` + `up -d --force-recreate api web` wiederholen.
|
umstellen und `pull` + `up -d --force-recreate api web` wiederholen.
|
||||||
|
|
||||||
|
## 10. Desktop-App: Pakete und Release-Dateien
|
||||||
|
|
||||||
|
Seit September 2026 gibt es Tessera zusätzlich als Desktop-App für Windows und
|
||||||
|
Linux. Dieses Kapitel beschreibt, woher die Pakete kommen, wo sie liegen und
|
||||||
|
wie Sie Fehlerbilder rund um den Download einordnen. Die Anwendersicht (Download,
|
||||||
|
Installation, SmartScreen-Hinweis, Bedienung) steht in
|
||||||
|
`docs/anleitung-anwender.md`, Kapitel „Desktop-App".
|
||||||
|
|
||||||
|
### Woher die Pakete kommen
|
||||||
|
|
||||||
|
Der CI-Job `desktop` läuft nach `test` und vor `publish` – auf Push nach `main`
|
||||||
|
und bei jedem Freigabe-Tag `v*`. In diesem einen Job entstehen auf dem
|
||||||
|
Linux-Runner sowohl das Linux-AppImage als auch der Windows-Installer per
|
||||||
|
Cross-Bau (`cargo-xwin` + NSIS aus dem Ubuntu-Paket, kein Windows-Rechner in
|
||||||
|
der Pipeline). Einzelheiten zur Werkzeugkette stehen in
|
||||||
|
[`docs/ci-cd-setup.md`](./ci-cd-setup.md), Abschnitt 4.
|
||||||
|
|
||||||
|
### Wo die Pakete im Abbild liegen
|
||||||
|
|
||||||
|
`publish` kopiert die fertigen Pakete in das API-Abbild nach
|
||||||
|
`/app/desktop-dist/`, zusammen mit einer `manifest.json` (Version, Kanal,
|
||||||
|
Dateinamen, Größen, Prüfsummen). Auf dem Beta-Kanal tragen die Dateinamen
|
||||||
|
zusätzlich den Suffix `-beta.{commit}`, zum Beispiel
|
||||||
|
`Tessera-Setup-1.1.0-beta.742fb5c.exe` und
|
||||||
|
`Tessera-1.1.0-beta.742fb5c.AppImage`; auf Live steht dort die reine Form
|
||||||
|
`Tessera-Setup-X.Y.Z.exe` / `Tessera-X.Y.Z.AppImage`.
|
||||||
|
|
||||||
|
Kontrolle auf dem Server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose exec api ls -l /app/desktop-dist
|
||||||
|
curl -s https://{ihre-adresse}/api-proxy/desktop/latest
|
||||||
|
```
|
||||||
|
|
||||||
|
`ls -l` zeigt die abgelegten Dateien samt `manifest.json`; die `curl`-Abfrage
|
||||||
|
liefert dieselben Angaben als JSON (Version, Kanal, je Plattform Dateiname,
|
||||||
|
Größe, Prüfsumme und relative Download-Adresse) – das ist genau die Antwort,
|
||||||
|
die auch die Anmeldeseite und die Einstellungsseite auswerten. Antwortet die
|
||||||
|
Abfrage mit `404`, fehlt entweder das Verzeichnis oder das Manifest; die
|
||||||
|
Web-Oberfläche blendet den Download-Link dann automatisch aus.
|
||||||
|
|
||||||
|
### Release-Dateien in Gitea
|
||||||
|
|
||||||
|
Bei einem Freigabe-Tag hängt die Pipeline zusätzlich beide Dateien aus dem
|
||||||
|
Manifest als Anhänge an den Gitea-Release desselben Tags (Kapitel 9, „Eine
|
||||||
|
Version freigeben") – idempotent: ein erneuter Lauf ersetzt eine bereits
|
||||||
|
vorhandene Datei gleichen Namens, statt einen zweiten Anhang anzulegen. Die am
|
||||||
|
Release hinterlegte Datei ist byteidentisch mit der im Abbild ausgelieferten;
|
||||||
|
die Prüfsumme (`sha256`) aus `manifest.json` gilt für beide gleichermaßen.
|
||||||
|
|
||||||
|
### Umgebungsvariablen
|
||||||
|
|
||||||
|
Für die Desktop-Auslieferung ist keine neue Pflichtvariable nötig.
|
||||||
|
|
||||||
|
| Variable | Pflicht? | Default | Zweck |
|
||||||
|
|----------|:---:|---|---|
|
||||||
|
| `DESKTOP_DIST_DIR` | nein | `/app/desktop-dist` (im Abbild) | Ablageort der Desktop-Pakete und der `manifest.json`, aus dem `GET /desktop/latest` und `GET /desktop/download/:platform` lesen. In der Regel nicht ändern. |
|
||||||
|
|
||||||
|
### Fehlerbilder
|
||||||
|
|
||||||
|
| Symptom | Wahrscheinliche Ursache | Prüfen / Beheben |
|
||||||
|
|---|---|---|
|
||||||
|
| Download-Link fehlt auf der Anmeldeseite bzw. `/api-proxy/desktop/latest` liefert `404` | Das laufende Abbild trägt keine Desktop-Pakete – der Job `publish` hätte ohne Manifest eigentlich abbrechen müssen | Den zugehörigen Pipeline-Lauf prüfen (Job `desktop`/`publish` grün?), danach `docker compose pull` + `up -d --force-recreate api` erneut ausführen. |
|
||||||
|
| Download bricht bei großen Dateien ab | Größengrenze oder Zeitlimit des vorgeschalteten Proxys (Nginx Proxy Manager) – `client_max_body_size` bzw. Timeout-Einstellungen | Proxy-Konfiguration für die betroffene Adresse prüfen und die Grenze anheben. |
|
||||||
|
| Client meldet „Unter dieser Adresse antwortet kein Tessera-Server" | Anwender hat die interne API-Adresse statt der Web-Adresse eingetragen, oder `/api-proxy` ist vom Client-Rechner aus nicht erreichbar | Die im Anwenderhandbuch beschriebene Adresse verwenden (dieselbe wie im Browser); Netzwerk-/Firewall-Erreichbarkeit der Web-Adresse prüfen. |
|
||||||
|
| Windows zeigt die SmartScreen-Warnung | Erwartet – die App ist für den internen Gebrauch nicht signiert (D-09) | Kein Fehler; Anwenderhandbuch, Abschnitt „Installation unter Windows", beschreibt den Ablauf. |
|
||||||
|
|||||||
@@ -30,15 +30,23 @@ fest verankert.
|
|||||||
apps/
|
apps/
|
||||||
api/ @tessera/api — NestJS-Backend
|
api/ @tessera/api — NestJS-Backend
|
||||||
web/ @tessera/web — Next.js-Frontend
|
web/ @tessera/web — Next.js-Frontend
|
||||||
desktop/ — — Tauri-Wrapper, früher Stand (nur Cargo-Projekt + eine setup.html)
|
desktop/ @tessera/desktop — Tauri-Desktop-Client (Windows/Linux), fertiges Produkt
|
||||||
packages/
|
packages/
|
||||||
shared/ @tessera/shared — geteilte Konstanten/Typen, derzeit sehr klein (APP_NAME, HealthResponse)
|
shared/ @tessera/shared — geteilte Konstanten/Typen, inzwischen auch die Manifest-Typen der Desktop-Pakete
|
||||||
module-sdk/ — — TypeScript-Interfaces für den Modul-Vertrag (TesseraModule, ModuleManifest)
|
module-sdk/ — — TypeScript-Interfaces für den Modul-Vertrag (TesseraModule, ModuleManifest)
|
||||||
```
|
```
|
||||||
|
|
||||||
`apps/desktop` besteht bislang nur aus dem Tauri-Grundgerüst (`src-tauri/`) und einer einzelnen
|
`apps/desktop` ist der fertige Desktop-Client (Tauri 2), kein Grundgerüst mehr:
|
||||||
`setup.html` — dort ist noch keine eigentliche Anwendung zu finden. `packages/shared` ist ebenfalls
|
`src-tauri/src/lib.rs` bündelt Tray-Menü, die beiden Erststart-Kommandos
|
||||||
minimal; es enthält aktuell nur eine Konstante und ein Health-Interface, keine DTOs.
|
(`check_server`/`save_server_url`) und die Versionsprüfung gegen den Server;
|
||||||
|
`src/setup.html` ist die eigenständige Erststart-Seite (kein Bundler, spricht
|
||||||
|
nur über `window.__TAURI__.core.invoke`). Die fertigen Installationspakete
|
||||||
|
(Windows-`.exe`, Linux-`.AppImage`) entstehen nicht lokal, sondern im CI-Job
|
||||||
|
`desktop` (siehe [Desktop-App lokal bauen](#desktop-app-lokal-bauen) für den
|
||||||
|
lokalen Linux-Bau). `packages/shared` bleibt schlank, trägt inzwischen aber
|
||||||
|
zusätzlich zu Konstante und Health-Interface auch die Typen für das
|
||||||
|
Desktop-Paket-Manifest (`DesktopPlatform`, `DesktopManifest`,
|
||||||
|
`DesktopLatestResponse`).
|
||||||
|
|
||||||
**Root-Skripte** (`package.json`, laufen über Turborepo durch alle Workspaces):
|
**Root-Skripte** (`package.json`, laufen über Turborepo durch alle Workspaces):
|
||||||
|
|
||||||
@@ -117,6 +125,46 @@ postgresql://tessera:tessera_dev@<container-ip>:5432/tessera
|
|||||||
Das ist relevant, sobald Sie `prisma migrate dev`, `prisma studio` oder ein manuelles `psql` **vom
|
Das ist relevant, sobald Sie `prisma migrate dev`, `prisma studio` oder ein manuelles `psql` **vom
|
||||||
Host aus** statt aus dem `api`-Container heraus ausführen wollen.
|
Host aus** statt aus dem `api`-Container heraus ausführen wollen.
|
||||||
|
|
||||||
|
### Desktop-App lokal bauen
|
||||||
|
|
||||||
|
Voraussetzungen zusätzlich zu oben:
|
||||||
|
|
||||||
|
- Rust (stable) über [rustup](https://rustup.rs/)
|
||||||
|
- Auf Ubuntu/Debian folgende Systempakete:
|
||||||
|
```bash
|
||||||
|
sudo apt-get install -y libwebkit2gtk-4.1-dev libjavascriptcoregtk-4.1-dev \
|
||||||
|
libayatana-appindicator3-dev librsvg2-dev libgtk-3-dev libssl-dev patchelf
|
||||||
|
```
|
||||||
|
|
||||||
|
Version setzen und Linux-Paket bauen:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sh .gitea/scripts/desktop-version.sh
|
||||||
|
pnpm --filter @tessera/desktop exec tauri build --bundles appimage
|
||||||
|
```
|
||||||
|
|
||||||
|
`desktop-version.sh` schreibt die Version des letzten Freigabe-Tags in
|
||||||
|
`tauri.conf.json`/`Cargo.toml` — die im Repository eingecheckten Versionsdateien
|
||||||
|
sind nur eine Basislinie, nicht die tatsächliche Freigabeversion. Das fertige
|
||||||
|
Paket liegt danach unter
|
||||||
|
`apps/desktop/src-tauri/target/release/bundle/appimage/`. Um es wie die
|
||||||
|
API es ausliefern würde einzusammeln:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sh .gitea/scripts/desktop-collect.sh --require linux
|
||||||
|
```
|
||||||
|
|
||||||
|
Das schreibt `desktop-dist/` (per `.gitignore` vom Git ausgeschlossen, bis auf
|
||||||
|
einen Platzhalter) samt `manifest.json`. Starten Sie danach den lokalen
|
||||||
|
Docker-Stack (`docker compose build api` genügt für die API allein), liefert
|
||||||
|
`GET /desktop/latest` die dort abgelegten Pakete aus.
|
||||||
|
|
||||||
|
**Der Windows-Installer wird nur im CI gebaut** (`cargo-xwin`-Cross-Bau, NSIS
|
||||||
|
aus dem Ubuntu-Paket `nsis` — siehe `docs/ci-cd-setup.md`, Abschnitt 4). Lokal
|
||||||
|
genügt für Rust-Änderungen `cargo check`/`cargo clippy` in
|
||||||
|
`apps/desktop/src-tauri`; einen Windows-Installer lokal zu bauen ist nicht
|
||||||
|
vorgesehen.
|
||||||
|
|
||||||
## Architektur im Überblick
|
## Architektur im Überblick
|
||||||
|
|
||||||
**Frontend** (`apps/web/src/app`, Next.js App Router):
|
**Frontend** (`apps/web/src/app`, Next.js App Router):
|
||||||
@@ -422,6 +470,27 @@ gegen Services, Controller-Logik und React-Komponenten. Guard-artige Spezifikati
|
|||||||
gegen bereits einmal aufgetretene Fehler — wiederkehrende Fallstricke werden in diesem Projekt
|
gegen bereits einmal aufgetretene Fehler — wiederkehrende Fallstricke werden in diesem Projekt
|
||||||
durch einen Test abgesichert, nicht nur durch einen Kommentar.
|
durch einen Test abgesichert, nicht nur durch einen Kommentar.
|
||||||
|
|
||||||
|
**Desktop-Modul (`apps/api/src/desktop`):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm --filter @tessera/api exec vitest run src/desktop
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Tests laufen als echter HTTP-Durchstich über `NestFactory.create()` +
|
||||||
|
`app.listen(0)` gegen ein echtes temporäres Verzeichnis (kein `fs`-Mock) —
|
||||||
|
Manifest lesen, 404 ohne Manifest, Plattform-Whitelist, Pfad-Traversal
|
||||||
|
abgewiesen.
|
||||||
|
|
||||||
|
**Rust (`apps/desktop/src-tauri`):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo check
|
||||||
|
cargo clippy
|
||||||
|
```
|
||||||
|
|
||||||
|
Beide laufen auch im CI-Job `desktop` (D-16); ein grüner `cargo clippy` ohne
|
||||||
|
Warnungen ist Voraussetzung für den Bauschritt.
|
||||||
|
|
||||||
## Konventionen und Fallstricke
|
## Konventionen und Fallstricke
|
||||||
|
|
||||||
**NestJS-Routenreihenfolge:** NestJS matcht Routen in Deklarationsreihenfolge. Eine statische Route
|
**NestJS-Routenreihenfolge:** NestJS matcht Routen in Deklarationsreihenfolge. Eine statische Route
|
||||||
|
|||||||
+115
-9
@@ -109,21 +109,71 @@ hardcoden.
|
|||||||
|
|
||||||
Die CI/CD-Pipeline (`.gitea/workflows/ci.yml`) laeuft bei jedem Push auf die
|
Die CI/CD-Pipeline (`.gitea/workflows/ci.yml`) laeuft bei jedem Push auf die
|
||||||
Zweige `main` und `live` sowie bei jedem Tag `v*` (z. B. `v1.0.0`) und besteht
|
Zweige `main` und `live` sowie bei jedem Tag `v*` (z. B. `v1.0.0`) und besteht
|
||||||
aus drei aufeinander aufbauenden Jobs:
|
aus vier aufeinander aufbauenden Jobs:
|
||||||
|
|
||||||
1. **quality** -- Lint und TypeScript Type-Check (Lint ist derzeit ein Leerlauf,
|
1. **quality** -- Lint und TypeScript Type-Check (Lint ist derzeit ein Leerlauf,
|
||||||
siehe WINDOWS #35; der Type-Check ist echt)
|
siehe WINDOWS #35; der Type-Check ist echt)
|
||||||
2. **test** -- Vitest Unit- und Integrationstests
|
2. **test** -- Vitest Unit- und Integrationstests
|
||||||
3. **publish** -- Docker Images mit Versionsstempel bauen und in die Gitea-Registry
|
3. **desktop** -- Desktop-Pakete fuer Windows und Linux bauen (nur auf `main`
|
||||||
|
und bei Tags `v*`, siehe unten)
|
||||||
|
4. **publish** -- Docker Images mit Versionsstempel bauen und in die Gitea-Registry
|
||||||
veroeffentlichen
|
veroeffentlichen
|
||||||
|
|
||||||
Ablauf: `quality` -> `test` -> `publish` (jeder Job nur bei Erfolg des
|
Ablauf: `quality` -> `test` -> `desktop` -> `publish` (jeder Job nur bei Erfolg
|
||||||
vorherigen). Der Job `publish` besteht aus vier Schritten: `actions/checkout@v4`
|
des vorherigen; `desktop` selbst laeuft nur, wenn die `if`-Bedingung zutrifft
|
||||||
mit `fetch-depth: 0` (volle Historie samt Tags, sonst liefert `git describe`
|
-- auf einem Push nach `live` ohne Tag entfaellt der Job, `publish` startet in
|
||||||
nichts), Login in die Registry (siehe Abschnitt 3), der Aufruf von
|
diesem Fall trotzdem, weil `needs: desktop` bei einem uebersprungenen Job nicht
|
||||||
`.gitea/scripts/publish-images.sh` und der Aufruf von
|
blockiert). Der Job `publish` besteht aus sechs Schritten:
|
||||||
`.gitea/scripts/publish-release.sh` (legt bei Tags `v*` den Gitea-Release aus dem
|
`actions/checkout@v4` mit `fetch-depth: 0` (volle Historie samt Tags, sonst
|
||||||
CHANGELOG-Abschnitt an; auf `main` endet er mit "nichts zu tun").
|
liefert `git describe` nichts), der Wiederherstellung der Desktop-Pakete aus
|
||||||
|
dem Zwischenspeicher, einer harten Pruefung des Manifests, Login in die
|
||||||
|
Registry (siehe Abschnitt 3), der Aufruf von `.gitea/scripts/publish-images.sh`
|
||||||
|
und der Aufruf von `.gitea/scripts/publish-release.sh` (legt bei Tags `v*` den
|
||||||
|
Gitea-Release aus dem CHANGELOG-Abschnitt an und haengt die Desktop-Pakete als
|
||||||
|
Dateien an; auf `main` endet er mit "nichts zu tun").
|
||||||
|
|
||||||
|
### Job `desktop`: Windows- und Linux-Pakete auf dem Linux-Runner
|
||||||
|
|
||||||
|
Der Job `desktop` baut auf demselben `ubuntu-latest`-Runner nacheinander (ein
|
||||||
|
Cache, ein Runner, siehe `18-CONTEXT.md` Specific Ideas) sowohl das
|
||||||
|
Linux-AppImage als auch -- per Cross-Bau -- den Windows-Installer:
|
||||||
|
|
||||||
|
1. **Systemabhaengigkeiten** (`apt-get install`): WebKit/Tauri-Pakete
|
||||||
|
(`libwebkit2gtk-4.1-dev` usw.) fuer den Linux-Bau, dazu `lld llvm clang
|
||||||
|
nsis` fuer den Windows-Cross-Bau (der NSIS-Bundler ruft `makensis` aus
|
||||||
|
genau diesem Paket auf).
|
||||||
|
2. **Rust-Toolchain** per `rustup` (kein Rust im Runner-Abbild), inklusive
|
||||||
|
`rustup component add clippy` -- `--profile minimal` installiert `clippy`
|
||||||
|
sonst nicht mit (siehe Fehlerbehebung unten).
|
||||||
|
3. **Cargo-Zwischenspeicher** (`actions/cache@v4`, Schluessel ueber den Hash
|
||||||
|
von `Cargo.lock`): `~/.cargo/registry`, `~/.cargo/git`,
|
||||||
|
`~/.cargo/bin/cargo-xwin`, `~/.cache/cargo-xwin` (die von `cargo-xwin`
|
||||||
|
heruntergeladene Windows-SDK-Ablage -- soll nur einmal geladen werden),
|
||||||
|
`~/.local/share/tauri` (NSIS-Plugins) und `apps/desktop/src-tauri/target`.
|
||||||
|
4. **Windows-Werkzeuge** -- bewusst NACH dem Cache-Wiederherstellungsschritt:
|
||||||
|
`rustup target add x86_64-pc-windows-msvc` und
|
||||||
|
`command -v cargo-xwin || cargo install --locked cargo-xwin`. Stuende
|
||||||
|
dieser Schritt vor der Cache-Wiederherstellung, wuerde `cargo-xwin` bei
|
||||||
|
jedem Lauf neu gebaut, selbst wenn der Cache es bereits enthaelt.
|
||||||
|
5. **Version setzen** (`desktop-version.sh`), **Rust pruefen**
|
||||||
|
(`cargo check`/`cargo clippy`, D-16), Bau des Linux-AppImage
|
||||||
|
(`tauri build --bundles appimage`), dann des Windows-Installers per
|
||||||
|
Cross-Bau (`tauri build --runner cargo-xwin --target
|
||||||
|
x86_64-pc-windows-msvc --bundles nsis`).
|
||||||
|
6. **Pakete einsammeln** (`desktop-collect.sh --require linux,windows`) --
|
||||||
|
schreibt `manifest.json` und schlaegt fehl, wenn eine der beiden Dateien
|
||||||
|
fehlt.
|
||||||
|
7. **Uebergabe an `publish`** per `actions/cache/save@v4` mit dem Schluessel
|
||||||
|
`desktop-dist-${{ gitea.sha }}` (ein neuer Schluessel je Commit, damit
|
||||||
|
`publish` garantiert die Pakete des gerade gebauten Standes bekommt, nicht
|
||||||
|
einen aelteren Cache-Treffer).
|
||||||
|
|
||||||
|
**Warum `actions/cache` und nicht `upload-artifact`:** Auf dieser
|
||||||
|
Gitea-Instanz ist `actions/upload-artifact`/`download-artifact` unzuverlaessig
|
||||||
|
(Erfahrungswert aus 18-02) -- die Uebergabe zwischen `desktop` und `publish`
|
||||||
|
laeuft deshalb bewusst ueber `actions/cache/save` und
|
||||||
|
`actions/cache/restore` mit `fail-on-cache-miss: true`, nicht ueber
|
||||||
|
Artefakt-Uploads.
|
||||||
|
|
||||||
Das Release-Skript spricht die Gitea-API ueber `GITHUB_API_URL` bzw.
|
Das Release-Skript spricht die Gitea-API ueber `GITHUB_API_URL` bzw.
|
||||||
`GITHUB_SERVER_URL/api/v1` an -- im Job-Container ist das
|
`GITHUB_SERVER_URL/api/v1` an -- im Job-Container ist das
|
||||||
@@ -237,3 +287,59 @@ und einen Branch-Schutz fuer `live` anlegen (T-KU1-04).
|
|||||||
2. Pruefen, ob der Tag wirklich gepusht wurde: `git ls-remote --tags origin`
|
2. Pruefen, ob der Tag wirklich gepusht wurde: `git ls-remote --tags origin`
|
||||||
3. `dev` bedeutet: das Image wurde ohne Build-Args gebaut (lokal statt ueber
|
3. `dev` bedeutet: das Image wurde ohne Build-Args gebaut (lokal statt ueber
|
||||||
das Skript) -- das ist fuer lokale Builds normal
|
das Skript) -- das ist fuer lokale Builds normal
|
||||||
|
|
||||||
|
### Job `desktop` schlaegt fehl
|
||||||
|
|
||||||
|
1. **`cargo clippy` meldet `'cargo-clippy' is not installed for the toolchain`**
|
||||||
|
-- der Schritt "Rust-Toolchain" installiert mit `--profile minimal`, das
|
||||||
|
`clippy` nicht mitbringt. Behoben durch `rustup component add clippy`
|
||||||
|
direkt nach der Toolchain-Installation (siehe oben); bei einem aehnlichen
|
||||||
|
Fehlerbild in Zukunft pruefen, ob diese Zeile noch vorhanden ist.
|
||||||
|
2. **apt-Paketname unbekannt / `pkg-config` findet eine Bibliothek nicht** --
|
||||||
|
Paketnamen aendern sich gelegentlich zwischen Ubuntu-Versionen des
|
||||||
|
Runner-Abbilds; den fehlenden `.pc`/`.so`-Namen aus der Fehlermeldung in
|
||||||
|
`apt-cache search` nachschlagen und die Paketliste im Schritt
|
||||||
|
"Systemabhaengigkeiten" ergaenzen.
|
||||||
|
3. **`openssl-sys` scheitert beim Windows-Cross-Ziel** -- OpenSSL laesst sich
|
||||||
|
fuer `x86_64-pc-windows-msvc` von Linux aus nicht ohne Weiteres
|
||||||
|
cross-kompilieren; falls eine neue Abhaengigkeit das ueber `openssl-sys`
|
||||||
|
statt `rustls-tls` einzieht, das Feature/die Abhaengigkeit auf
|
||||||
|
`rustls-tls` umstellen (in diesem Job bislang nicht aufgetreten, `reqwest`
|
||||||
|
ist bereits auf `rustls-tls` konfiguriert).
|
||||||
|
4. **NSIS-Plugin-Download schlaegt fehl** -- Tauris NSIS-Bundler laedt beim
|
||||||
|
ersten Bau zusaetzliche Plugins nach `~/.local/share/tauri`; ein
|
||||||
|
Netzwerkfehler dort bricht den Bauschritt "Windows-Installer bauen
|
||||||
|
(Cross-Bau)" ab. Lauf erneut anstossen; bleibt der Cache warm, entfaellt
|
||||||
|
der Download beim naechsten Mal.
|
||||||
|
5. **Runner-Speicher/-Zeit reicht nicht** -- der Rust-Bau laeuft auf einem
|
||||||
|
begrenzten Runner (8 Kerne/15 GB, siehe `18-CONTEXT.md`); bei
|
||||||
|
Speicherdruck `CARGO_BUILD_JOBS` (z. B. auf `4`) als Umgebungsvariable im
|
||||||
|
Job setzen, um die parallele Uebersetzung zu drosseln.
|
||||||
|
|
||||||
|
### `publish`: cache miss
|
||||||
|
|
||||||
|
`actions/cache/restore@v4` mit `fail-on-cache-miss: true` bricht den Job
|
||||||
|
`publish` hart ab, wenn kein Eintrag unter dem Schluessel
|
||||||
|
`desktop-dist-${{ gitea.sha }}` existiert. Wahrscheinlichste Ursachen:
|
||||||
|
|
||||||
|
1. Der Job `desktop` ist fehlgeschlagen oder uebersprungen worden (siehe
|
||||||
|
`if`-Bedingung oben) -- im Gitea-Actions-Lauf pruefen, ob `desktop`
|
||||||
|
tatsaechlich gruen war.
|
||||||
|
2. Der Zwischenspeicher-Server des `act_runner` ist nicht erreichbar oder
|
||||||
|
nicht aktiviert -- Runner-Konfiguration pruefen (Abschnitt 2,
|
||||||
|
`[cache] enabled` muss gesetzt sein).
|
||||||
|
3. Der Commit-SHA im Schluessel weicht zwischen den Jobs ab (sollte bei
|
||||||
|
`gitea.sha` innerhalb desselben Laufs nicht vorkommen) -- bei Verdacht die
|
||||||
|
Job-Logs beider Schritte (`Uebergabe an publish` in `desktop`,
|
||||||
|
`Desktop-Pakete aus dem Zwischenspeicher holen` in `publish`) auf den
|
||||||
|
verwendeten Schluessel vergleichen.
|
||||||
|
|
||||||
|
### Release-Upload 413
|
||||||
|
|
||||||
|
Schlaegt der Datei-Upload in `publish-release.sh` mit HTTP 413 (Datei zu
|
||||||
|
gross) fehl, blockt vermutlich der vorgeschaltete Proxy vor `git.vicolab.de`
|
||||||
|
den grossen Installer-Upload -- dasselbe bekannte Verhalten wie beim
|
||||||
|
Image-Push (Abschnitt 3). Abhilfe: `GITEA_API` auf die Host-Adresse
|
||||||
|
`http://172.18.0.1:3002/api/v1` setzen, damit der Release-Upload denselben
|
||||||
|
Weg wie der Registry-Push nimmt und den Proxy umgeht -- nur noetig, wenn der
|
||||||
|
Proxy die Groesse tatsaechlich abweist.
|
||||||
|
|||||||
Reference in New Issue
Block a user