From 3d8c0660f73b0988a74749ac75af4e4627345b52 Mon Sep 17 00:00:00 2001 From: Schalli Date: Thu, 25 Jun 2026 09:15:43 +0200 Subject: [PATCH] docs(06): research phase domain - Tauri 2.x desktop wrapper + Gitea Actions CI/CD --- .../06-desktop-client-ci-cd/06-RESEARCH.md | 757 ++++++++++++++++++ 1 file changed, 757 insertions(+) create mode 100644 .planning/phases/06-desktop-client-ci-cd/06-RESEARCH.md diff --git a/.planning/phases/06-desktop-client-ci-cd/06-RESEARCH.md b/.planning/phases/06-desktop-client-ci-cd/06-RESEARCH.md new file mode 100644 index 0000000..9febb7e --- /dev/null +++ b/.planning/phases/06-desktop-client-ci-cd/06-RESEARCH.md @@ -0,0 +1,757 @@ +# Phase 6: Desktop Client & CI/CD - Research + +**Researched:** 2026-06-25 +**Domain:** Tauri 2.x Desktop Wrapper + Gitea Actions CI/CD +**Confidence:** MEDIUM + +## Summary + +Phase 6 consists of two independent tracks: (A) a Tauri 2.x desktop wrapper that loads the existing Tessera web frontend via URL, and (B) Gitea-based DevOps tooling with CI/CD pipelines. The desktop app is a thin shell -- no frontend code is bundled, it simply opens a WebView pointing at the configured server URL. All desktop-specific behavior (system tray, notifications, window state, autostart) is handled by official Tauri plugins with minimal custom Rust code. + +The Gitea Actions CI/CD track uses GitHub Actions-compatible YAML syntax in `.gitea/workflows/`. An `act_runner` container executes pipeline jobs in Docker. The pipeline runs Lint + TypeCheck, Tests (Vitest), Docker image builds, and auto-deploys via `docker-compose pull && restart` on the same server. This is purely developer tooling -- no Git/Gitea features are exposed in the Tessera application. + +A critical prerequisite: **Rust toolchain and WebKitGTK development headers are NOT installed** on the current machine. These must be installed before any Tauri development or builds can proceed. The system has GTK3, librsvg2, and libayatana-appindicator3 (runtime libs), but the **-dev packages for WebKitGTK and the Rust compiler are missing**. + +**Primary recommendation:** Install Rust via rustup and WebKitGTK dev headers first, then scaffold `apps/desktop` with `npx tauri init`, configure `frontendDist` to the server URL, and wire up four official Tauri plugins (store, notification, autostart, window-state). For CI/CD, add `act_runner` to docker-compose and create workflow files in `.gitea/workflows/`. + + + +## User Constraints (from CONTEXT.md) + +### Locked Decisions +- **D-01:** Wrapper mit System Tray und nativen OS-Benachrichtigungen (z.B. Kalender-Erinnerungen). Braucht Backend-Integration fuer Notification-Events. +- **D-02:** Server-URL konfigurierbar beim ersten Start. Wird lokal gespeichert. Ein Build fuer alle Umgebungen. +- **D-03:** Beim Schliessen des Fensters (X-Button) wird in System Tray minimiert. Beenden nur ueber Tray-Kontextmenu. +- **D-04:** Autostart-Option in Desktop-App-Einstellungen vorhanden, standardmaessig deaktiviert. +- **D-05:** Fensterposition und -groesse werden beim Schliessen gespeichert, beim naechsten Start wiederhergestellt. Erster Start: 1280x800 zentriert. +- **D-06:** Eigenes Tessera-App-Icon (basierend auf Design-System, OKLCH Farben). +- **D-07:** Linux: AppImage als primaeres Format (distro-uebergreifend). +- **D-08:** Update-Hinweis: App prueft beim Start ob neue Version verfuegbar, zeigt Notification. Download bleibt manuell. Auto-Update kann spaeter nachgeruestet werden. +- **D-09:** Code Signing kommt spaeter -- erst relevant wenn Tessera an externe Kunden verkauft wird. Fuer interne Nutzung ohne Signierung OK. +- **D-10:** Gitea ist reines Versionskontroll- und CI/CD-Tooling. Tessera hat keine Git/Gitea-Funktionalitaet in der App. +- **D-11:** Claude pusht manuell bei Meilensteinen oder grossen Bugfixes nach Gitea. Kein automatischer Sync, kein zeitgesteuerter Push. +- **D-12:** Gitea Actions CI/CD Pipeline wird bei jedem Push getriggert: Lint + TypeCheck -> Tests (Vitest) -> Docker Images bauen -> Auto-Deploy. +- **D-13:** Deploy-Ziel ist gleicher Server wie Gitea. Pipeline macht docker-compose pull + restart. + +### Claude's Discretion +- Titelleiste: Nativ vs. Custom -- basierend auf Aufwand und Design-System +- App-Menueleiste: Kein Menu vs. minimales Menu -- basierend auf Plattform-Konventionen +- Windows Installer-Format: MSI vs. NSIS -- basierend auf Zielgruppe (intern, spaeter extern) +- Gitea Actions Workflow-Struktur und Stage-Konfiguration +- Docker Image Registry: Gitea-intern vs. lokal + +### Deferred Ideas (OUT OF SCOPE) +- Auto-Update mit Tauri Updater +- Code Signing +- macOS Support +- Gitea Webhooks fuer externe Events + + + + + +## Phase Requirements + +| ID | Description | Research Support | +|----|-------------|------------------| +| DESK-01 | Tauri-basierter Desktop-Wrapper fuer Windows und Linux | Tauri 2.x with `frontendDist` URL loading, AppImage (Linux) and NSIS (Windows) bundle targets | +| DESK-02 | Desktop-App verbindet sich mit dem Web-Backend (kein eigenstaendiger Server) | `frontendDist` set to configurable external URL, tauri-plugin-store for persisting server URL | +| INFRA-04 | Automatisierte Gitea-Integration (Commits, Pushes, Merges) | Gitea Actions with act_runner in Docker, workflow in `.gitea/workflows/`, auto-deploy via docker-compose | + + + +## Architectural Responsibility Map + +| Capability | Primary Tier | Secondary Tier | Rationale | +|------------|-------------|----------------|-----------| +| WebView shell (URL loading) | Desktop Client (Tauri/Rust) | -- | Tauri owns the native window and WebView; web content comes from the server | +| System tray + close-to-tray | Desktop Client (Tauri/Rust) | -- | OS-level integration via TrayIconBuilder, window event interception | +| Native notifications | Desktop Client (Tauri/Rust) | API / Backend | Tauri sends OS notifications; backend must provide notification events (future WebSocket/SSE) | +| Server URL configuration | Desktop Client (Tauri/Rust) | -- | First-run dialog and persistent storage are purely client-side | +| Window state persistence | Desktop Client (Tauri/Rust) | -- | tauri-plugin-window-state handles save/restore automatically | +| Autostart | Desktop Client (Tauri/Rust) | -- | OS-level registration via tauri-plugin-autostart | +| Version check | Desktop Client (Tauri/Rust) | API / Backend | Desktop fetches version endpoint from API; comparison logic in Rust/JS | +| CI/CD pipeline | Infrastructure (Gitea/Docker) | -- | Gitea Actions + act_runner, entirely outside the application | +| Docker image builds | Infrastructure (Gitea/Docker) | -- | Existing Dockerfiles reused by CI pipeline | +| Auto-deploy | Infrastructure (Gitea/Docker) | -- | docker-compose pull + restart on same server | + +## Standard Stack + +### Core + +| Library | Version | Purpose | Why Standard | +|---------|---------|---------|--------------| +| Tauri | 2.x (2.11+) | Desktop shell | 5MB installer vs Electron 150MB, OS-native WebView, Rust backend. Project-locked decision [CITED: CLAUDE.md] | +| @tauri-apps/cli | 2.11.3 | Build tooling | Official Tauri CLI for init, dev, build commands [VERIFIED: npm registry + official docs] | +| @tauri-apps/api | 2.11.1 | JS bridge | Frontend-to-Rust IPC, tray, window management APIs [VERIFIED: npm registry + official docs] | +| Rust toolchain | stable (1.77.2+) | Compilation | Required by Tauri; installed via rustup [CITED: v2.tauri.app/start/prerequisites/] | + +### Tauri Plugins + +| Library | Version | Purpose | Why Standard | +|---------|---------|---------|--------------| +| @tauri-apps/plugin-store | 2.4.3 | Persistent KV storage | Stores server URL, app preferences. Official plugin [VERIFIED: npm registry + v2.tauri.app/plugin/store/] | +| @tauri-apps/plugin-notification | 2.3.3 | OS notifications | Native desktop notifications for calendar reminders etc. Official plugin [VERIFIED: npm registry + v2.tauri.app/plugin/notification/] | +| @tauri-apps/plugin-autostart | 2.5.1 | System startup | Register/unregister app for OS autostart. Official plugin [VERIFIED: npm registry + v2.tauri.app/plugin/autostart/] | +| @tauri-apps/plugin-window-state | 2.4.1 | Window persistence | Auto-saves/restores window size, position, maximized state. Official plugin [VERIFIED: npm registry + v2.tauri.app/plugin/window-state/] | + +### CI/CD Infrastructure + +| Component | Version | Purpose | Why Standard | +|-----------|---------|---------|--------------| +| Gitea Actions | (built into Gitea) | CI/CD engine | GitHub Actions-compatible YAML, self-hosted [CITED: docs.gitea.com/usage/actions/act-runner] | +| act_runner | latest | Workflow executor | Official Gitea runner, executes jobs in Docker containers [CITED: docs.gitea.com/usage/actions/act-runner] | +| docker/build-push-action | v6 | Image build | Standard GitHub/Gitea action for Docker builds [ASSUMED] | +| appleboy/ssh-action | v1 | Remote deploy | SSH into deploy target for docker-compose operations [ASSUMED] | + +### Alternatives Considered + +| Instead of | Could Use | Tradeoff | +|------------|-----------|----------| +| NSIS (Windows) | MSI (WiX) | MSI only builds on Windows; NSIS supports cross-compilation from Linux. **Recommendation: NSIS** -- enables building Windows installer from the Linux dev machine [CITED: v2.tauri.app/distribute/windows-installer/] | +| Native title bar | Custom title bar | Custom requires more effort and platform testing. **Recommendation: Native** -- simpler, consistent with OS, less Rust code | +| No menu bar | Minimal menu bar | Linux desktop conventions expect a menu. **Recommendation: No menu bar** -- wrapper app has no app-specific actions; tray menu suffices | +| Gitea internal registry | Local Docker images | Internal registry adds complexity. **Recommendation: Local builds** -- act_runner builds images on same server, no push needed. Use `docker compose build` directly | + +**Installation (Desktop app):** +```bash +# System prerequisites (Debian/Ubuntu/Trixie) +sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file \ + libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev + +# Rust toolchain +curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh + +# Tauri CLI + plugins (from monorepo root) +pnpm add -D @tauri-apps/cli --filter=@tessera/desktop +pnpm add @tauri-apps/api @tauri-apps/plugin-store @tauri-apps/plugin-notification \ + @tauri-apps/plugin-autostart @tauri-apps/plugin-window-state --filter=@tessera/desktop +``` + +## Package Legitimacy Audit + +| Package | Registry | Age | Downloads | Source Repo | Verdict | Disposition | +|---------|----------|-----|-----------|-------------|---------|-------------| +| @tauri-apps/cli | npm | 3+ yrs (org) | 1.78M/wk | github.com/tauri-apps/tauri | OK* | Approved -- official Tauri project, SUS flag due to recent version publish only | +| @tauri-apps/api | npm | 3+ yrs (org) | 2.11M/wk | github.com/tauri-apps/tauri | OK* | Approved -- official Tauri project, SUS flag due to recent version publish only | +| @tauri-apps/plugin-store | npm | 2+ yrs | 236K/wk | github.com/tauri-apps/plugins-workspace | OK | Approved | +| @tauri-apps/plugin-notification | npm | 2+ yrs | 293K/wk | github.com/tauri-apps/plugins-workspace | OK | Approved | +| @tauri-apps/plugin-autostart | npm | 2+ yrs | 108K/wk | github.com/tauri-apps/plugins-workspace | OK | Approved | +| @tauri-apps/plugin-window-state | npm | 2+ yrs | 73K/wk | github.com/tauri-apps/plugins-workspace | OK | Approved | + +*`@tauri-apps/cli` and `@tauri-apps/api` were flagged SUS by the legitimacy check due to recent publish dates (2026-06-17/19). These are false positives -- both are official packages from the tauri-apps organization with millions of weekly downloads and verified source repos. The "too-new" signal is triggered by the latest version publish, not the package itself. + +**Packages removed due to [SLOP] verdict:** none +**Packages flagged as suspicious [SUS]:** @tauri-apps/cli and @tauri-apps/api (false positive -- official packages, override to Approved) + +## Architecture Patterns + +### System Architecture Diagram + +``` +Desktop App (Tauri) Server Infrastructure ++---------------------------+ +---------------------------+ +| | | | +| Rust Backend | HTTP | Next.js (apps/web) | +| - TrayIconBuilder |--------->| - Server Components | +| - Window event handler | | - App Router | +| - Plugin orchestration | | | +| - Version check logic | +---------------------------+ +| | | +| WebView (OS native) | | internal +| - Loads server URL | +---------------------------+ +| - No bundled assets | | NestJS (apps/api) | +| | | - REST API | +| Local Storage | | - /health | +| - tauri-plugin-store | | - /version (new) | +| - server-url.json | +---------------------------+ +| - preferences.json | | ++---------------------------+ +---------------------------+ + | PostgreSQL (db) | +CI/CD Pipeline (Gitea) +---------------------------+ ++---------------------------+ +| .gitea/workflows/ci.yml | +| 1. Lint + TypeCheck | +| 2. Tests (Vitest) | +| 3. Docker Build | +| 4. Deploy (compose pull) | ++---------------------------+ + | + act_runner (Docker) + - Executes jobs + - Docker socket mount +``` + +### Recommended Project Structure + +``` +apps/ +├── desktop/ # NEW: Tauri desktop wrapper +│ ├── package.json # @tessera/desktop +│ ├── src-tauri/ +│ │ ├── Cargo.toml # Rust dependencies + Tauri plugins +│ │ ├── tauri.conf.json # Window config, bundle targets, plugins +│ │ ├── capabilities/ +│ │ │ └── default.json # Plugin permissions +│ │ ├── icons/ # App icons (PNG, ICO) +│ │ │ ├── icon.png +│ │ │ ├── icon.ico +│ │ │ └── 128x128.png # Tray icon +│ │ └── src/ +│ │ ├── lib.rs # Plugin setup, tray, window events +│ │ └── main.rs # Entry point (generated) +│ └── src/ # Minimal JS for first-run setup page +│ └── setup.html # Server URL configuration form +├── web/ # Existing Next.js app +└── api/ # Existing NestJS API + +.gitea/ +└── workflows/ + └── ci.yml # CI/CD pipeline definition +``` + +### Pattern 1: URL-Loading WebView Wrapper + +**What:** Tauri app that loads an external web URL instead of bundling frontend assets. +**When to use:** When the desktop app is a thin shell around an existing web application. + +```json +// Source: v2.tauri.app/reference/config/ +// tauri.conf.json +{ + "build": { + "frontendDist": "http://localhost:3000", + "devUrl": "http://localhost:3000" + }, + "app": { + "withGlobalTauri": true, + "windows": [ + { + "label": "main", + "title": "Tessera", + "width": 1280, + "height": 800, + "center": true, + "decorations": true, + "resizable": true + } + ] + }, + "bundle": { + "active": true, + "targets": ["appimage", "nsis"], + "icon": ["icons/icon.png", "icons/icon.ico"] + } +} +``` + +**Key insight:** `frontendDist` set to a URL means no assets are embedded. The app must have a configurable URL mechanism -- on first launch, show a setup page (local HTML) where user enters the server URL, then store it via tauri-plugin-store and load the WebView with that URL. + +### Pattern 2: Close-to-Tray with System Tray + +**What:** Intercept window close, hide to system tray instead of quitting. +**When to use:** Long-running desktop apps that should stay accessible via tray icon. + +```rust +// Source: v2.tauri.app/learn/system-tray/ + GitHub discussions +use tauri::{ + image::Image, + menu::{MenuBuilder, MenuItem}, + tray::TrayIconBuilder, + Manager, WindowEvent, RunEvent, +}; + +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_store::Builder::new().build()) + .plugin(tauri_plugin_notification::init()) + .plugin(tauri_plugin_window_state::Builder::default().build()) + .plugin(tauri_plugin_autostart::init( + tauri_plugin_autostart::MacosLauncher::LaunchAgent, + None, + )) + .setup(|app| { + // Build tray menu + let open = MenuItem::with_id(app, "open", "Oeffnen", true, None::<&str>)?; + let quit = MenuItem::with_id(app, "quit", "Beenden", true, None::<&str>)?; + let menu = MenuBuilder::new(app) + .item(&open) + .separator() + .item(&quit) + .build()?; + + // Create tray icon + TrayIconBuilder::new() + .icon(Image::from_bytes(include_bytes!("../icons/icon.png"))?) + .tooltip("Tessera") + .menu(&menu) + .show_menu_on_left_click(false) + .on_menu_event(|app, event| match event.id.as_ref() { + "open" => { + if let Some(w) = app.get_webview_window("main") { + let _ = w.show(); + let _ = w.set_focus(); + } + } + "quit" => app.exit(0), + _ => {} + }) + .on_tray_icon_event(|tray, event| { + if let tauri::tray::TrayIconEvent::Click { + button: tauri::tray::MouseButton::Left, + button_state: tauri::tray::MouseButtonState::Up, + .. + } = event + { + let app = tray.app_handle(); + if let Some(w) = app.get_webview_window("main") { + let _ = w.show(); + let _ = w.set_focus(); + } + } + }) + .build(app)?; + + Ok(()) + }) + // Intercept window close -> hide to tray + .on_window_event(|window, event| { + if let WindowEvent::CloseRequested { api, .. } = event { + window.hide().unwrap(); + api.prevent_close(); + } + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} +``` + +### Pattern 3: Configurable Server URL with First-Run Setup + +**What:** On first launch, show a local HTML page for server URL input. Store URL persistently. +**When to use:** When desktop app connects to a configurable server (D-02). + +```rust +// In setup callback, check if server URL exists +let store = app.store("config.json")?; +let server_url = store.get("server_url"); + +if let Some(url) = server_url { + // Load the configured server URL + let window = app.get_webview_window("main").unwrap(); + window.navigate(url.as_str().unwrap().parse().unwrap()); +} else { + // Show local setup page (bundled HTML) + // User enters URL -> JS calls store.set -> navigates to URL +} +``` + +```javascript +// Source: v2.tauri.app/plugin/store/ +// setup.html JavaScript +import { load } from '@tauri-apps/plugin-store'; + +async function saveAndConnect(url) { + const store = await load('config.json', { autoSave: true }); + await store.set('server_url', url); + // Navigate main window to server + window.location.href = url; +} +``` + +### Pattern 4: Gitea Actions Multi-Stage Pipeline + +**What:** CI/CD pipeline triggered on push with staged quality gates. +**When to use:** Automated build/test/deploy workflow. + +```yaml +# .gitea/workflows/ci.yml +# Source: docs.gitea.com/usage/actions/ + botmonster.com guide +name: Tessera CI/CD + +on: + push: + branches: [main] + +jobs: + quality: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 9 + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm lint + - run: pnpm type-check + + test: + runs-on: ubuntu-latest + needs: quality + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 9 + - uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm test + + build-deploy: + runs-on: ubuntu-latest + needs: test + steps: + - uses: actions/checkout@v4 + - name: Build and deploy + run: | + docker compose build web api + docker compose up -d web api +``` + +### Anti-Patterns to Avoid + +- **Bundling frontend assets in Tauri:** Since Tessera desktop is a wrapper, do NOT copy the Next.js build into the Tauri binary. Use `frontendDist` with a URL. This keeps the desktop app tiny and always shows current server content. +- **Using MSI for Windows builds from Linux:** MSI (WiX) only builds on Windows. Always use NSIS for cross-platform build capability. [CITED: v2.tauri.app/distribute/windows-installer/] +- **Docker-in-Docker for CI builds:** The act_runner already runs inside Docker. Mount the host Docker socket (`/var/run/docker.sock`) instead of running Docker inside Docker. This avoids complexity and security issues. [CITED: docs.gitea.com/usage/actions/act-runner] +- **Complex registry setup:** Since deploy target is the same server, skip pushing to a registry. Build images locally with `docker compose build` and restart services. + +## Don't Hand-Roll + +| Problem | Don't Build | Use Instead | Why | +|---------|-------------|-------------|-----| +| Window state persistence | Manual size/position save/restore | tauri-plugin-window-state | Handles all edge cases (multi-monitor, maximized state, DPI changes) | +| Persistent KV storage | Custom file I/O in Rust | tauri-plugin-store | Handles serialization, atomic writes, cross-platform paths | +| System autostart | Manual registry/plist/desktop-file creation | tauri-plugin-autostart | Cross-platform (Windows registry, Linux .desktop, macOS LaunchAgent) | +| OS notifications | Custom dbus/Win32 notification code | tauri-plugin-notification | Permission handling, cross-platform API, Tauri-integrated | +| CI/CD workflow engine | Custom shell scripts | Gitea Actions | YAML-based, GitHub Actions compatible, Docker-native execution | +| SSH deployment | Manual SSH scripting in CI | appleboy/ssh-action | Handles key auth, connection management, error reporting | + +**Key insight:** Every desktop behavior (tray, notifications, autostart, window state) has an official Tauri plugin. The custom Rust code is only ~100 lines of glue connecting these plugins with the close-to-tray behavior and first-run setup flow. + +## Common Pitfalls + +### Pitfall 1: Missing WebKitGTK Dev Headers +**What goes wrong:** `cargo build` fails with "pkg-config could not find webkit2gtk-4.1" +**Why it happens:** Linux runtime libraries (libwebkit2gtk) are installed but the -dev headers for compilation are not. +**How to avoid:** Install `libwebkit2gtk-4.1-dev` via apt before any Tauri build. The current machine is missing this. +**Warning signs:** `pkg-config --modversion webkit2gtk-4.1` returns "not found" + +### Pitfall 2: Tauri App Exits When Window Hidden +**What goes wrong:** App quits entirely when user clicks X, even with close-to-tray configured. +**Why it happens:** Tauri default behavior exits when all windows are destroyed. Hiding a window triggers destroy. +**How to avoid:** Must handle BOTH `WindowEvent::CloseRequested` (call `api.prevent_close()` + `window.hide()`) AND potentially `RunEvent::ExitRequested` (call `api.prevent_exit()`). Both handlers are needed for reliable close-to-tray. [CITED: github.com/tauri-apps/tauri/discussions/2684] +**Warning signs:** App process disappears from task manager after clicking X + +### Pitfall 3: frontendDist URL vs. Configurable URL Conflict +**What goes wrong:** `frontendDist` in tauri.conf.json is a static URL, but the app needs a user-configurable server URL. +**Why it happens:** `frontendDist` is a build-time config, not runtime. +**How to avoid:** Set `frontendDist` to a local setup page (or use a custom scheme). On app start in Rust, read the stored URL from tauri-plugin-store and call `window.navigate(url)` to redirect to the actual server. The first-run page handles initial URL configuration. [ASSUMED] +**Warning signs:** All builds hardcoded to one server URL + +### Pitfall 4: Gitea Actions docker/build-push-action JWT Parse Error +**What goes wrong:** `docker/build-push-action@v4` fails because Gitea's ACTIONS_RUNTIME_TOKEN is not a JWT. +**Why it happens:** Gitea generates a random string token, not a JWT like GitHub. The action tries to parse it as JWT. +**How to avoid:** Use plain `docker compose build` commands instead of the build-push-action, OR use v6 which may have fixed this. Since we build locally (no registry push), plain commands are simpler anyway. [CITED: docs.gitea.com/usage/actions/comparison] +**Warning signs:** "JWT parse error" in CI logs + +### Pitfall 5: act_runner Docker Socket Security +**What goes wrong:** Malicious workflow code could access Docker daemon and inspect other containers. +**Why it happens:** `/var/run/docker.sock` is mounted into the runner container for Docker-in-Docker builds. +**How to avoid:** Since this is an internal-only CI (Claude pushes manually), the risk is acceptable. For extra safety, use ephemeral runners (`GITEA_RUNNER_EPHEMERAL=1`) which revoke credentials after each job. [CITED: docs.gitea.com/usage/actions/act-runner] +**Warning signs:** Runner container has persistent access to Docker daemon + +### Pitfall 6: pnpm Workspace Tauri Init Confusion +**What goes wrong:** `tauri init` or `create-tauri-app` runs from wrong directory, creates files in wrong location. +**Why it happens:** Monorepo structure requires precise directory targeting. +**How to avoid:** Create `apps/desktop/` manually, cd into it, run `pnpm create tauri-app .` or `npx tauri init`. Ensure package.json has `name: "@tessera/desktop"`. Add to pnpm-workspace.yaml (already covered by `apps/*` glob). [ASSUMED] +**Warning signs:** src-tauri appears at monorepo root instead of apps/desktop/ + +## Code Examples + +### First-Run Setup Page (Bundled HTML) + +```html + + + + + + Tessera - Setup + + + +
+

Tessera

+

Server-URL eingeben:

+ + +
+ + + +``` + +### Cargo.toml Plugin Dependencies + +```toml +# Source: Official Tauri plugin docs +# apps/desktop/src-tauri/Cargo.toml +[dependencies] +tauri = { version = "2", features = ["tray-icon"] } +tauri-plugin-store = "2" +tauri-plugin-notification = "2" +tauri-plugin-autostart = "2" +tauri-plugin-window-state = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +``` + +### Capabilities Configuration + +```json +// Source: v2.tauri.app/plugin/autostart/ + notification/ + store/ +// apps/desktop/src-tauri/capabilities/default.json +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Tessera desktop capabilities", + "windows": ["main"], + "permissions": [ + "core:default", + "store:default", + "notification:default", + "notification:allow-is-permission-granted", + "notification:allow-request-permission", + "notification:allow-notify", + "autostart:allow-enable", + "autostart:allow-disable", + "autostart:allow-is-enabled", + "window-state:default" + ] +} +``` + +### Version Check Endpoint (API Side) + +```typescript +// Source: Pattern for D-08 update hint +// apps/api/src/health/health.controller.ts (extend existing) +@Get('version') +getVersion() { + return { + version: process.env.npm_package_version || '0.0.1', + name: 'tessera', + }; +} +``` + +```rust +// Desktop side: check version on startup +// In setup callback after loading server URL +async fn check_version(server_url: &str, current_version: &str) -> Option { + let url = format!("{}/version", server_url); + if let Ok(resp) = reqwest::get(&url).await { + if let Ok(info) = resp.json::().await { + let remote = info["version"].as_str().unwrap_or("0.0.0"); + if remote != current_version { + return Some(remote.to_string()); + } + } + } + None +} +``` + +## State of the Art + +| Old Approach | Current Approach | When Changed | Impact | +|--------------|------------------|--------------|--------| +| Tauri 1.x SystemTray API | Tauri 2.x TrayIconBuilder API | Oct 2024 (Tauri 2.0 stable) | New API: `tauri::tray::TrayIconBuilder`, feature flag renamed `tray-icon` [CITED: v2.tauri.app/blog/tauri-20/] | +| `@tauri-apps/api/notification` | `@tauri-apps/plugin-notification` | Tauri 2.0 | Notifications moved from core API to plugin [CITED: v2.tauri.app/start/migrate/from-tauri-1/] | +| WiX MSI only | NSIS + MSI options | Tauri 2.0 | NSIS is now the recommended Windows installer with cross-compilation support [CITED: v2.tauri.app/distribute/windows-installer/] | +| Gitea without CI | Gitea Actions (since 1.19) | 2023 | Built-in CI/CD, no need for external Jenkins/Drone [CITED: docs.gitea.com/usage/actions/act-runner] | + +**Deprecated/outdated:** +- Tauri 1.x APIs: All `SystemTray`, `CustomMenuItem`, `SystemTrayMenu` APIs replaced +- `@tauri-apps/api/notification`: Replaced by `@tauri-apps/plugin-notification` +- `tauri::api::shell`: Removed in Tauri 2, use `tauri-plugin-shell` instead + +## Assumptions Log + +| # | Claim | Section | Risk if Wrong | +|---|-------|---------|---------------| +| A1 | frontendDist can be overridden at runtime via window.navigate() for configurable URL | Architecture Patterns / Pitfall 3 | Setup flow would need different approach (custom protocol or build-time injection) | +| A2 | appleboy/ssh-action@v1 works with Gitea Actions | Standard Stack (CI/CD) | Would need alternative SSH deployment method (plain script) | +| A3 | docker/build-push-action@v6 compatibility with Gitea Actions | Pitfall 4 | Use plain docker commands instead (already the recommendation) | +| A4 | pnpm create tauri-app works inside existing monorepo subdirectory | Pitfall 6 | May need manual scaffolding of src-tauri directory structure | +| A5 | Tauri tray icon bundle format (.png) works on both Linux and Windows | Code Examples | May need separate icon formats per platform | +| A6 | reqwest crate available for HTTP requests in Tauri Rust backend | Code Examples (version check) | Could use tauri-plugin-http or JS-side fetch instead | + +## Open Questions + +1. **Backend Notification Events (D-01)** + - What we know: Tauri can send OS notifications via plugin. Desktop app is a WebView wrapper. + - What's unclear: How the backend pushes notification events to the desktop app. WebSocket? SSE? Polling? The web app already runs in the WebView, so standard web push patterns apply. + - Recommendation: Use the web app's existing event mechanism (if any) or add a simple polling endpoint. Full WebSocket/SSE for notifications can be deferred since the web app itself would also benefit from it. + +2. **First-Run Setup Page Architecture** + - What we know: Need to show a local page for URL input before loading external URL. + - What's unclear: Whether to use a bundled HTML file, a custom Tauri protocol, or handle it entirely in Rust with a simple dialog. + - Recommendation: Bundle a minimal `setup.html` that uses tauri-plugin-store. Set `frontendDist` to this page. After URL is saved, navigate to the server URL. + +3. **Gitea Instance Access** + - What we know: The project has no git remotes configured. Gitea should be on the same server. + - What's unclear: Whether Gitea is already running, its URL, and whether Actions are enabled. + - Recommendation: Document Gitea setup as a prerequisite. If not running, add to docker-compose or assume existing instance. + +## Environment Availability + +| Dependency | Required By | Available | Version | Fallback | +|------------|------------|-----------|---------|----------| +| Rust toolchain | Tauri compilation | **NO** | -- | Must install via rustup | +| libwebkit2gtk-4.1-dev | Tauri Linux build | **NO** | -- | Must install via apt | +| libssl-dev | Tauri dependencies | Check needed | -- | Must install via apt | +| libxdo-dev | Tauri Linux features | Check needed | -- | Must install via apt | +| build-essential | Compilation | Check needed | -- | Must install via apt | +| Node.js | Frontend tooling | YES | v24.16.0 | -- | +| pnpm | Package manager | YES | 9.15.0 | -- | +| Docker | Container builds | YES | 29.5.3 | -- | +| Docker Compose | Service orchestration | YES | v5.1.4 | -- | +| GTK3 (runtime) | Tauri runtime | YES | 3.24.49 | -- | +| librsvg2 (runtime) | Icon rendering | YES | 2.60.0 | -- | +| libayatana-appindicator3 (runtime) | System tray | YES | 0.5.94 | -- | +| Gitea instance | CI/CD | Unknown | -- | Must verify or set up | +| act_runner | CI job execution | Unknown | -- | Add to docker-compose | + +**Missing dependencies with no fallback:** +- Rust toolchain (rustc, cargo) -- MUST install before any Tauri work +- libwebkit2gtk-4.1-dev -- MUST install for Linux compilation +- Other -dev packages (libssl-dev, libxdo-dev, build-essential) -- MUST install + +**Missing dependencies with fallback:** +- Gitea instance -- if not available, can defer CI/CD track or add to docker-compose + +## Validation Architecture + +### Test Framework + +| Property | Value | +|----------|-------| +| Framework | Vitest 3.x | +| Config file | `apps/web/vitest.config.ts` (existing) | +| Quick run command | `pnpm --filter=@tessera/web test` | +| Full suite command | `pnpm test` | + +### Phase Requirements to Test Map + +| Req ID | Behavior | Test Type | Automated Command | File Exists? | +|--------|----------|-----------|-------------------|-------------| +| DESK-01 | Tauri app builds for Linux (AppImage) and Windows (NSIS) | manual | `cd apps/desktop && pnpm tauri build` | N/A -- build verification | +| DESK-02 | Desktop connects to web backend via URL | manual | Verify WebView loads configured URL | N/A -- runtime verification | +| INFRA-04 | CI pipeline runs on push | integration | Push to Gitea, verify Actions run | N/A -- infrastructure verification | +| D-02 | Server URL stored persistently | smoke | Launch app, set URL, restart, verify URL retained | N/A -- manual smoke test | +| D-03 | Close minimizes to tray | smoke | Click X, verify tray icon, verify process alive | N/A -- manual smoke test | +| D-05 | Window state persisted | smoke | Resize, close, reopen, verify dimensions | N/A -- manual smoke test | + +### Sampling Rate +- **Per task commit:** `pnpm lint && pnpm type-check` (no Tauri-specific unit tests expected) +- **Per wave merge:** `pnpm test` (full suite) +- **Phase gate:** Manual smoke test of desktop app + CI pipeline run + +### Wave 0 Gaps +- No automated tests planned for desktop wrapper -- Tauri apps are best validated via manual smoke testing (WebView behavior, tray interaction, OS-level features) +- CI pipeline validation requires a running Gitea instance +- Existing web/api tests should continue passing (regression) + +## Security Domain + +### Applicable ASVS Categories + +| ASVS Category | Applies | Standard Control | +|---------------|---------|-----------------| +| V2 Authentication | no | Auth handled by web app (Phase 2), not desktop shell | +| V3 Session Management | no | Sessions managed by web app cookies in WebView | +| V4 Access Control | no | Access control in NestJS API layer | +| V5 Input Validation | yes | Server URL input validation (URL format, https preference) | +| V6 Cryptography | no | No crypto in desktop wrapper | + +### Known Threat Patterns for Tauri Desktop + +| Pattern | STRIDE | Standard Mitigation | +|---------|--------|---------------------| +| Malicious server URL injection | Tampering | Validate URL format, warn on non-HTTPS, store in plugin-store (not plaintext config) | +| WebView navigation hijacking | Spoofing | Lock WebView to configured server domain via CSP in tauri.conf.json | +| Docker socket exposure (CI) | Elevation | Ephemeral runners, internal-only CI, no untrusted code | +| CI secrets leakage | Information Disclosure | Use Gitea secrets store, never log secrets in pipeline output | + +## Sources + +### Primary (HIGH confidence) +- [v2.tauri.app/start/prerequisites/](https://v2.tauri.app/start/prerequisites/) - Linux system dependencies +- [v2.tauri.app/reference/config/](https://v2.tauri.app/reference/config/) - Configuration reference (frontendDist, windows, bundle) +- [v2.tauri.app/learn/system-tray/](https://v2.tauri.app/learn/system-tray/) - TrayIconBuilder API, menu events +- [v2.tauri.app/plugin/store/](https://v2.tauri.app/plugin/store/) - Plugin store API +- [v2.tauri.app/plugin/notification/](https://v2.tauri.app/plugin/notification/) - Notification plugin +- [v2.tauri.app/plugin/autostart/](https://v2.tauri.app/plugin/autostart/) - Autostart plugin +- [v2.tauri.app/plugin/window-state/](https://v2.tauri.app/plugin/window-state/) - Window state plugin +- [v2.tauri.app/distribute/windows-installer/](https://v2.tauri.app/distribute/windows-installer/) - NSIS vs MSI +- [docs.gitea.com/usage/actions/act-runner](https://docs.gitea.com/usage/actions/act-runner) - Runner setup +- [docs.gitea.com/usage/actions/comparison](https://docs.gitea.com/usage/actions/comparison) - Gitea vs GitHub Actions + +### Secondary (MEDIUM confidence) +- [npm registry](https://www.npmjs.com/package/@tauri-apps/cli) - Package versions verified +- [melvinoostendorp.nl/blog/tauri-v2-nextjs-monorepo-guide](https://melvinoostendorp.nl/blog/tauri-v2-nextjs-monorepo-guide) - Monorepo structure reference +- [botmonster.com/posts/self-hosted-cicd-pipeline-gitea-actions-docker/](https://botmonster.com/posts/self-hosted-cicd-pipeline-gitea-actions-docker/) - Gitea Actions Docker pipeline example +- [github.com/tauri-apps/tauri/discussions/2684](https://github.com/tauri-apps/tauri/discussions/2684) - Close-to-tray pattern + +### Tertiary (LOW confidence) +- Training knowledge for frontendDist runtime override via window.navigate() (A1) +- Training knowledge for reqwest crate usage in Tauri (A6) + +## Project Constraints (from CLAUDE.md) + +- **Docker-based infrastructure:** All components as containers. CI/CD deploys via docker-compose. +- **Mandantenfaehigkeit:** Multi-tenancy architecture. Desktop wrapper is tenant-agnostic (URL-based). +- **Claude builds everything:** Architecture must be understandable. Desktop app code should be minimal Rust. +- **pnpm workspaces:** Desktop app integrates as `apps/desktop` in existing monorepo structure. +- **Biome for linting:** CI pipeline must run Biome (not ESLint) for lint step. +- **Vitest for tests:** CI pipeline uses Vitest, matching existing test infrastructure. +- **TypeScript 5.5+:** Type-check step in CI uses existing turbo task. + +## Metadata + +**Confidence breakdown:** +- Standard stack: MEDIUM - All packages verified on npm registry and documented on official Tauri docs. Tauri 2 is stable (released Oct 2024). Package versions confirmed. +- Architecture: MEDIUM - URL-loading wrapper pattern is documented. Close-to-tray pattern confirmed via multiple community examples. First-run setup flow uses assumed runtime URL override. +- Pitfalls: HIGH - WebKitGTK dependency confirmed missing via environment probe. JWT parse error documented in Gitea Actions comparison docs. + +**Research date:** 2026-06-25 +**Valid until:** 2026-07-25 (stable technology, 30-day validity)