--- phase: 18-desktop-client-fertigstellen plan: 03 subsystem: ui tags: [next-intl, react, desktop-distribution, i18n] # Dependency graph requires: - phase: 18-01 provides: "GET /desktop/latest, GET /desktop/download/:platform (beide @Public()), DesktopLatestResponse-Form" provides: - "apps/web/src/lib/desktop.ts (loadDesktopLatest, desktopDownloadUrl, formatFileSize)" - "DesktopDownloadLinks — unauffaelliger Link-Block auf der Anmeldeseite (D-12)" - "DesktopAppSettings + Route /settings/general/desktop — Version, Download-Knoepfe, Dateigroesse, Erklaerung" - "Seitenleisteneintrag Desktop-App unter Allgemein" affects: [18-04-client-updateprüfung, 18-06-browser-gegenprobe] actuals: tokens: 6993 tasks: 2 commits: 2 plan_head_before: 2164cd537a8645f39a055bfa3cff77b8ef02822d tech-stack: added: [] patterns: - "memoisiertes Single-Promise-Laden (Modul-Ebene), still bei Fehler -> null, konsumiert per useEffect+useState mit active-Schutz (Muster app-version.ts/AppVersionBadge, jetzt zweimal wiederverwendet: Login-Link und Einstellungsseite)" - "Linkziel immer aus API_URL plus relativem url-Feld gebaut, nie eine absolute Adresse aus der Antwort uebernommen (T-18-07)" key-files: created: - 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/desktop/desktop-download-links.test.tsx - "apps/web/src/app/(portal)/settings/general/desktop/page.tsx" - apps/web/src/components/settings/desktop-app-settings.tsx - apps/web/src/components/settings/desktop-app-settings.test.tsx modified: - "apps/web/src/app/(auth)/login/page.tsx" - apps/web/src/components/settings/settings-sidebar.tsx - apps/web/src/messages/de.json - apps/web/src/messages/en.json - apps/web/src/messages/umlaut-dictionary.ts key-decisions: - "useLocale() aus der Anmeldeseiten-Komponente entfernt (Plan-Text erwaehnte es, aber der Link-Block zeigt keine Dateigroesse — nur die Einstellungsseite braucht locale fuer formatFileSize); vermeidet eine ungenutzte Variable." - "'neuere' zur UMLAUT_ALLOWLIST ergaenzt — der bestehende Waechter-Test flaggte das Wort faelschlich, weil es zufaellig die Buchstabenfolge 'ue' enthaelt, obwohl die Schreibweise bereits korrekt ist (kein Substitutionsfehler)." patterns-established: [] requirements-completed: [DESK-03] coverage: - id: D1 description: "Anmeldeseite zeigt Download-Link(s) nur wenn /desktop/latest antwortet, Windows fuehrt, Linux als Kurzlink bei beiden Paketen" requirement: "DESK-03" verification: - kind: unit ref: "apps/web/src/lib/desktop.test.ts#Test 1-5" status: pass - kind: unit ref: "apps/web/src/components/desktop/desktop-download-links.test.tsx#Test 1-3" status: pass human_judgment: false - id: D2 description: "Einstellungsseite Desktop-App: Version, zwei Primaerknoepfe mit Symbol, Dateiname/Groesse, Beta-Hinweis, vier erklaerende Saetze, Hinweistext ohne Pakete" requirement: "DESK-03" verification: - kind: unit ref: "apps/web/src/components/settings/desktop-app-settings.test.tsx#Test 1-3" status: pass human_judgment: false - id: D3 description: "Seitenleiste zeigt den Eintrag Desktop-App unter Allgemein mit aria-current auf der Route" requirement: "DESK-03" verification: - kind: other ref: "grep -c 'href=\"/settings/general/desktop\"' apps/web/src/components/settings/settings-sidebar.tsx (=1)" status: pass human_judgment: false - id: D4 description: "de/en vollstaendig fuer auth.desktopDownload.* und settings.desktop.*/categoryDesktopApp, deutsche Texte mit echten Umlauten" requirement: "DESK-03" verification: - kind: other ref: "node i18n-Paritaets-/Umlautskript aus 18-03-PLAN.md -> 'i18n OK'" status: pass - kind: unit ref: "apps/web/src/messages/umlaut-guard.spec.ts" status: pass human_judgment: false - id: D5 description: "Alle Downloads laufen ueber die Tessera-API (API_URL + relatives url-Feld), keine feste Server-/Firmenadresse im Code" requirement: "DESK-03" verification: - kind: unit ref: "apps/web/src/lib/desktop.test.ts#Test 4 (desktopDownloadUrl)" status: pass - kind: other ref: "grep -v '^\\s*//' apps/web/src/lib/desktop.ts | grep -c 'process.env.NEXT_PUBLIC_API_URL' (=1)" status: pass human_judgment: true rationale: "Der End-zu-Ende-Beweis (Browser klickt echten Download bis zum tatsaechlichen Dateidownload) ist die geplante Browser-Gegenprobe am Phasenende (18-06) — hier nur die Unit-/Text-Ebene automatisiert bewiesen." duration: 20min completed: 2026-09-16 status: complete --- # Phase 18 Plan 03: Desktop-App in der Web-Oberflaeche Summary **Unauffaelliger Download-Link auf der Anmeldeseite und eine vollstaendige Einstellungsseite "Desktop-App" (Version, zwei Primaerknoepfe mit Plattform-Symbol, Dateigroesse, Beta-Hinweis, vier erklaerende Saetze) — beide lesen `GET /desktop/latest` und blenden sich ohne Pakete aus.** ## Performance - **Duration:** 20 min (geschaetzt) - **Started:** 2026-09-16T14:10:00Z (geschaetzt) - **Completed:** 2026-09-16T14:30:47Z - **Tasks:** 2 - **Files modified:** 12 ## Accomplishments - `apps/web/src/lib/desktop.ts`: `loadDesktopLatest()` memoisiert (ein Fetch je Modulinstanz, still bei Fehler -> `null`, kein `credentials: 'include'` — die Anmeldeseite hat noch kein Cookie), `desktopDownloadUrl()` (baut die Adresse ausschliesslich aus `API_URL` plus dem relativen `url`-Feld, T-18-07), `formatFileSize()` (lokalisierte MB-Werte, `Intl.NumberFormat`). - `DesktopDownloadLinks` auf der Anmeldeseite: rendert nichts ohne Daten oder ohne Plattform in `files`; Windows fuehrt als Hauptlink, Linux folgt als kleiner Zusatzlink, wenn beide Pakete vorliegen; darunter die Versionszeile. - `DesktopAppSettings` unter `/settings/general/desktop`: vier erklaerende Saetze in Sie-Form (Was ist die App, Erststart, Tray-Verhalten, Update-Hinweis), Versionszeile, Beta-Kanal-Zusatzhinweis mit Commit, zwei Primaerknoepfe (`bg-primary`, inline-SVG-Plattformsymbol, `download`-Attribut) mit Dateiname+Groesse darunter, und ein Hinweistext statt der Knoepfe, wenn die API `null` liefert. - Seitenleiste: neuer Eintrag "Desktop-App" unter "Allgemein" mit identischem `aria-current`-Muster wie "Konto". - `de.json`/`en.json`: `auth.desktopDownload.*` und `settings.desktop.*`/`settings.categoryDesktopApp` vollstaendig, deutsche Texte mit echten Umlauten. ## Task Commits Each task was committed atomically: 1. **Task 1: Fetch-Helfer und der Download-Link auf der Anmeldeseite** - `026d9c3` (feat) 2. **Task 2: Einstellungsseite "Desktop-App" mit Knoepfen, Groesse und Erklaerung** - `e96d460` (feat) **Plan metadata:** commit pending (this SUMMARY + STATE.md/ROADMAP.md/REQUIREMENTS.md) ## Files Created/Modified - `apps/web/src/lib/desktop.ts` - `DesktopPlatform`/`DesktopFileInfo`/`DesktopLatestInfo`, `loadDesktopLatest`, `desktopDownloadUrl`, `formatFileSize` - `apps/web/src/lib/desktop.test.ts` - 5 Tests (memoisiert, still bei ok=false/Netzfehler, URL-Bau, Groessenformatierung) - `apps/web/src/components/desktop/desktop-download-links.tsx` - Link-Block der Anmeldeseite - `apps/web/src/components/desktop/desktop-download-links.test.tsx` - 3 Tests (leer, beide Plattformen, nur Linux) - `apps/web/src/app/(auth)/login/page.tsx` - `DesktopDownloadLinks` nach dem Formular eingebaut - `apps/web/src/app/(portal)/settings/general/desktop/page.tsx` - Route, delegiert an `DesktopAppSettings` - `apps/web/src/components/settings/desktop-app-settings.tsx` - Version, Knoepfe, Groesse, Saetze, Hinweisfall - `apps/web/src/components/settings/desktop-app-settings.test.tsx` - 3 Tests (beide Plattformen, Beta-Hinweis, keine Pakete) - `apps/web/src/components/settings/settings-sidebar.tsx` - Eintrag "Desktop-App" ergaenzt - `apps/web/src/messages/de.json` / `en.json` - `auth.desktopDownload.*`, `settings.desktop.*`, `settings.categoryDesktopApp` - `apps/web/src/messages/umlaut-dictionary.ts` - `neuere` zur Allowlist ergaenzt (Deviation, siehe unten) ## Decisions Made - `useLocale()` in `DesktopDownloadLinks` weggelassen: der Link-Block der Anmeldeseite zeigt keine Dateigroesse, nur die Version — `formatFileSize` wird ausschliesslich auf der Einstellungsseite gebraucht. Eine ungenutzte Variable haette keinen Wert gehabt. - Die vier erklaerenden Saetze und die Download-Bloecke bleiben eine einzige Client-Komponente (`DesktopAppSettings`) statt mehrerer Unterkomponenten — passend zur Groesse des Inhalts und zum bestehenden `account`/`smtp`-Seitenmuster (eine Komponente pro Einstellungsseite). ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] Umlaut-Waechter-Test schlug auf "neuere" fehl** - **Found during:** Task 2 (voller `pnpm --filter @tessera/web exec vitest run` nach dem i18n-Block) - **Issue:** `src/messages/umlaut-guard.spec.ts` flaggte `settings.desktop.update: "neuere"` als vermeintlich falsche ASCII-Umschrift, weil das Wort die Buchstabenfolge "ue" enthaelt (n-e-**ue**-r-e) — die Schreibweise ist aber bereits korrektes Deutsch, keine Substitution noetig. - **Fix:** `neuere` zur `UMLAUT_ALLOWLIST` in `apps/web/src/messages/umlaut-dictionary.ts` ergaenzt (neben den bereits vorhandenen `neue`/`neuen`/`Neue`/`Neues`). - **Files modified:** `apps/web/src/messages/umlaut-dictionary.ts` - **Verification:** `pnpm --filter @tessera/web exec vitest run` — vollstaendige Suite gruen (365/365). - **Committed in:** `e96d460` (Task 2 commit) --- **Total deviations:** 1 auto-fixed (1 bug) **Impact on plan:** Reine Testinfrastruktur-Korrektur, kein Verhaltensunterschied im Produktionscode. Keine Ausweitung des Umfangs. ## Issues Encountered None. ## User Setup Required None - no external service configuration required. ## Next Phase Readiness - Die Web-Oberflaeche liest `GET /desktop/latest` an beiden vorgesehenen Stellen (Anmeldeseite, Einstellungen) und blendet sich korrekt aus, wenn keine Pakete hinterlegt sind — 18-04 (Client-Versionspruefung) kann auf demselben Endpunkt aufbauen, ohne die Web-Seite zu beruehren. - Die Browser-Gegenprobe (echter Klick, echter Download) ist bewusst auf 18-06 verschoben (siehe Plan-`` Punkt 4); alle automatisierten Ebenen (Unit-Tests, Typpruefung, i18n-Paritaet/Umlaute, volle Web-Suite) sind gruen. - Kein Blocker. --- *Phase: 18-desktop-client-fertigstellen* *Completed: 2026-09-16* ## Self-Check: PASSED All created files verified on disk (`apps/web/src/lib/desktop.ts`, `desktop.test.ts`, `apps/web/src/components/desktop/desktop-download-links.tsx`, `desktop-download-links.test.tsx`, `apps/web/src/app/(portal)/settings/general/desktop/page.tsx`, `apps/web/src/components/settings/desktop-app-settings.tsx`, `desktop-app-settings.test.tsx`). Both task commits found in `git log` (`026d9c3`, `e96d460`). All plan-level `` items re-run and passing: `pnpm --filter @tessera/web exec vitest run` (365/365 green, baseline 354 + 11 new), `pnpm --filter @tessera/web type-check` (clean), i18n parity/umlaut script -> `i18n OK`. Browser-Gegenprobe bleibt fuer 18-06 (Plan-`` Punkt 4, ausserhalb dieses Plans).