# 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)