44154a4697
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
17 KiB
17 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, user_setup, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | user_setup | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 06-desktop-client-ci-cd | 02 | execute | 2 |
|
|
false |
|
|
Purpose: Completes DESK-01/DESK-02 — a user can install a real, well-behaved Tessera desktop app that lives in the tray, remembers its window, optionally autostarts, and warns about updates.
Output: A buildable Tauri app producing unsigned AppImage (Linux) and NSIS (Windows) installers
with full native integration, plus a /health/version API endpoint.
<execution_context> @$HOME/.claude/gsd-core/workflows/execute-plan.md @$HOME/.claude/gsd-core/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/06-desktop-client-ci-cd/06-CONTEXT.md @.planning/phases/06-desktop-client-ci-cd/06-RESEARCH.md @.planning/phases/06-desktop-client-ci-cd/06-01-SUMMARY.md @apps/api/src/health/health.controller.ts @apps/web/src/app/globals.css<artifacts_this_phase_produces>
apps/apiGET /health/version→{ version, name }(consumed by desktop version check)apps/desktop/src-tauri/src/lib.rs::run()— extended with tray, close-to-tray, 4 plugins, version checkapps/desktop/src-tauri/icons/{icon.png,icon.ico,128x128.png}— branded app/tray icons- Autostart toggle UI hook (invoked from within the loaded web app via tauri-plugin-autostart API) </artifacts_this_phase_produces>
Extend `lib.rs::run()` registering all four plugins on the builder: `tauri_plugin_store` (already present),
`tauri_plugin_notification::init()`, `tauri_plugin_window_state::Builder::default().build()`, and
`tauri_plugin_autostart::init(tauri_plugin_autostart::MacosLauncher::LaunchAgent, None)`. Window-state plugin
auto-handles D-05 save/restore; the 1280x800 centered first-run default already lives in tauri.conf.json.
In `.setup`, build the tray (D-03): a `MenuBuilder` with items `open` ("Oeffnen") and `quit` ("Beenden")
separated by a separator. Create `TrayIconBuilder` with the bundled icon bytes (icon.png from Task 3),
tooltip "Tessera", `show_menu_on_left_click(false)`. `on_menu_event`: "open" → show + set_focus the `main`
window; "quit" → `app.exit(0)`. `on_tray_icon_event`: left-click Up → show + focus `main` window.
Add close-to-tray (D-03, Pitfall 2): `.on_window_event` handling `WindowEvent::CloseRequested` by calling
`window.hide()` then `api.prevent_close()`. ALSO handle the exit path so the process stays alive when the
last window hides — implement the `RunEvent::ExitRequested` guard (call `api.prevent_exit()`) via
`.build(...).run(|_app, event| ...)` so the only real exit path is the tray "Beenden" → `app.exit(0)`.
Add the version check (D-08): after reading server_url in setup, spawn an async task that GETs
`{server_url}/health/version`, parses JSON `version`, compares against the app's own
`env!("CARGO_PKG_VERSION")`; if different/newer, send a native notification via the notification plugin with a
German body (e.g. title "Tessera Update", body "Eine neue Version ist verfuegbar."). Download stays manual.
Extend `capabilities/default.json` permissions to add: `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`
(keep existing `core:default`, `store:default`). These grant the loaded web app (D-04 autostart toggle) and
Rust side the needed plugin access.
cd apps/desktop/src-tauri && grep -q "TrayIconBuilder" src/lib.rs && grep -q "prevent_close" src/lib.rs && grep -q "prevent_exit" src/lib.rs && grep -q "health/version" src/lib.rs && grep -q "autostart:allow-enable" capabilities/default.json && grep -q "window-state:default" capabilities/default.json && cargo check
- `cargo check` passes with all four plugins registered
- lib.rs handles BOTH `prevent_close` (CloseRequested) AND `prevent_exit` (ExitRequested) per Pitfall 2
- lib.rs fetches `{server_url}/health/version` and notifies on version mismatch
- capabilities grants autostart + notification + window-state permissions
cargo check passes; tray + close-to-tray + window-state + autostart + version-check wired; D-01/03/04/05/08 covered in code
Task 3: Generate Tessera-branded icon and configure unsigned AppImage + NSIS bundles
apps/desktop/src-tauri/icons/icon.png, apps/desktop/src-tauri/icons/icon.ico, apps/desktop/src-tauri/icons/128x128.png, apps/desktop/src-tauri/tauri.conf.json
- apps/desktop/src-tauri/tauri.conf.json (existing bundle config from 06-01 — targets already ["appimage","nsis"])
- apps/web/src/app/globals.css (OKLCH tokens: --primary oklch(0.91 0.19 102) yellow, dark bg oklch(0.17 0.01 260) — icon palette per D-06)
- .planning/phases/06-desktop-client-ci-cd/06-RESEARCH.md (bundle targets, NSIS over MSI rationale, Pitfall A5 icon format)
Create a Tessera app icon (D-06) derived from the OKLCH design system: a simple square mark using primary
yellow `oklch(0.91 0.19 102)` on the dark `oklch(0.17 0.01 260)` background (e.g. a stylized "T" or tessera
tile). Author a master 1024x1024 PNG (place a source SVG/PNG under `apps/desktop/src-tauri/icons/` or
`scratchpad`), then generate the platform icon set with `pnpm --filter=@tessera/desktop tauri icon `,
which produces `icon.png`, `icon.ico`, `128x128.png`, and the platform variants Tauri expects. If `tauri icon`
cannot run headless, fall back to producing icon.png (1024), 128x128.png, and icon.ico manually with
ImageMagick (`convert`) and note it in SUMMARY.
Confirm `tauri.conf.json` `bundle.icon` references the generated `icons/icon.png` and `icons/icon.ico`, and
`bundle.targets` is `["appimage", "nsis"]` (D-07 AppImage primary; NSIS chosen over MSI per RESEARCH so the
Windows installer can cross-compile from Linux). Ensure the tray icon bytes referenced in lib.rs (Task 2)
point at an existing icon file (e.g. `icons/128x128.png` or `icons/icon.png`).
Per D-09, do NOT configure code signing — internal use ships unsigned by design; signing is deferred until
Tessera is sold externally. Leave all `bundle` signing/certificate fields unset so builds produce unsigned
AppImage and NSIS artifacts.
test -f apps/desktop/src-tauri/icons/icon.png && test -f apps/desktop/src-tauri/icons/icon.ico && grep -q '"appimage"' apps/desktop/src-tauri/tauri.conf.json && grep -q '"nsis"' apps/desktop/src-tauri/tauri.conf.json && file apps/desktop/src-tauri/icons/icon.png | grep -qi png
- `icons/icon.png` and `icons/icon.ico` exist and are valid image files
- `bundle.targets` includes both `appimage` and `nsis`
- Icon palette uses the OKLCH primary/dark colors from the design system
- No code-signing/certificate fields configured (D-09 unsigned for internal use)
Branded icon set generated; bundle configured for unsigned AppImage + NSIS
Task 4: Build the desktop app and verify native behaviors
- .planning/phases/06-desktop-client-ci-cd/06-RESEARCH.md (Validation Architecture — desktop smoke tests for D-02/D-03/D-05; Pitfall 2 warning signs)
The full native desktop wrapper: tray, close-to-tray, window-state, autostart, notifications, version check,
branded icon, AppImage + NSIS bundles. Tauri desktop behavior cannot be unit-tested (RESEARCH Wave 0 Gaps) —
it requires manual smoke verification of OS-level integration.
Before this checkpoint the executor runs (and reports output of):
- `pnpm --filter=@tessera/api type-check` and `pnpm --filter=@tessera/web type-check` (regression)
- `cd apps/desktop && pnpm tauri build` — produces the Linux AppImage (Windows NSIS may require a Windows
runner / cross toolchain; if NSIS cross-build is unavailable on this Linux host, report it and treat the
Windows target as build-config-verified only, AppImage as the proven artifact for DESK-01)
With the dev web stack running, launch the app (`pnpm --filter=@tessera/desktop tauri dev` or the built AppImage):
1. First run shows the German setup page; enter `http://localhost:3000` → Tessera web app loads (DESK-02)
2. Click the window X → window disappears but a Tessera tray icon remains; the process is still alive (D-03)
3. Right-click tray → "Oeffnen" restores the window; "Beenden" fully exits the process (D-03)
4. Resize/move the window, close to tray and quit, relaunch → window restores prior size/position (D-05)
5. Confirm an Autostart toggle is reachable and defaults to off (D-04)
6. If the server reports a different /health/version, a native update notification appears (D-08)
7. Confirm `apps/desktop/src-tauri/target/release/bundle/appimage/*.AppImage` exists (DESK-01)
- Setup → server URL → web app loads (DESK-02)
- X hides to tray, process alive; "Beenden" exits (D-03)
- Window state restored on relaunch (D-05)
- An AppImage artifact is produced (DESK-01)
Type "approved" or describe which behavior failed
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| desktop shell → API /health/version | Desktop fetches version info from the configured server over HTTP |
| loaded web app → autostart plugin | The web UI (D-04 toggle) invokes OS-level autostart registration via granted capability |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-06-04 | Spoofing | /health/version response | accept | Version string is non-sensitive; worst case is a spurious update notice. No action taken on it (manual download only, D-08) |
| T-06-05 | Elevation | autostart capability exposed to WebView | accept | Internal-only app loading a trusted self-hosted server; autostart enable/disable is the only granted surface, default disabled (D-04) |
| T-06-06 | Denial of Service | startup version fetch blocks UI | mitigate | Run the version check in a spawned async task; never block window display on the HTTP call |
| T-06-10 | Spoofing | unsigned installer artifacts (D-09) | accept | Code signing deferred for internal-only distribution; revisit before external sales. Documented, time-bound risk |
| T-06-SC | Tampering | cargo/npm installs | mitigate | All Tauri plugins [Approved] in RESEARCH Package Legitimacy Audit; reqwest is a mainstream crate, pin to 0.12 |
| </threat_model> |
<success_criteria>
- Close-to-tray, tray menu, window-state, autostart, notification + version check all functioning (D-01/03/04/05/08)
- Branded icon present (D-06); unsigned AppImage + NSIS bundle targets configured (D-07, D-09)
- API exposes /health/version (supports D-08)
- DESK-01 (installable Windows/Linux wrapper) and DESK-02 (connects to web backend) satisfied </success_criteria>