44154a4697
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
247 lines
14 KiB
Markdown
247 lines
14 KiB
Markdown
---
|
|
phase: 06-desktop-client-ci-cd
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- apps/desktop/package.json
|
|
- apps/desktop/src-tauri/Cargo.toml
|
|
- apps/desktop/src-tauri/tauri.conf.json
|
|
- apps/desktop/src-tauri/build.rs
|
|
- apps/desktop/src-tauri/src/main.rs
|
|
- apps/desktop/src-tauri/src/lib.rs
|
|
- apps/desktop/src-tauri/capabilities/default.json
|
|
- apps/desktop/src/setup.html
|
|
autonomous: false
|
|
requirements:
|
|
- DESK-01
|
|
- DESK-02
|
|
user_setup: []
|
|
|
|
must_haves:
|
|
truths:
|
|
- "Tauri toolchain prerequisites (Rust + WebKitGTK dev headers) are installed and verifiable"
|
|
- "apps/desktop is a Tauri 2.x project registered in the pnpm workspace as @tessera/desktop"
|
|
- "On first launch the app shows a local setup page where the user enters a server URL"
|
|
- "The entered server URL is persisted via tauri-plugin-store and reused on subsequent launches"
|
|
- "After a URL is configured, the WebView loads the configured Tessera server (no bundled frontend assets)"
|
|
artifacts:
|
|
- path: "apps/desktop/src-tauri/tauri.conf.json"
|
|
provides: "Tauri window + bundle config, frontendDist pointing at local setup page"
|
|
contains: "productName"
|
|
- path: "apps/desktop/src-tauri/src/lib.rs"
|
|
provides: "Rust entry: plugin registration, store read, navigate-to-server-URL logic"
|
|
min_lines: 25
|
|
- path: "apps/desktop/src/setup.html"
|
|
provides: "First-run server URL configuration page using tauri-plugin-store"
|
|
contains: "server_url"
|
|
- path: "apps/desktop/src-tauri/capabilities/default.json"
|
|
provides: "Plugin permission grants for store"
|
|
contains: "store:default"
|
|
key_links:
|
|
- from: "apps/desktop/src/setup.html"
|
|
to: "tauri-plugin-store (config.json)"
|
|
via: "load() + store.set('server_url', url)"
|
|
pattern: "server_url"
|
|
- from: "apps/desktop/src-tauri/src/lib.rs"
|
|
to: "main WebView window"
|
|
via: "read stored server_url, navigate window to it on startup"
|
|
pattern: "server_url"
|
|
---
|
|
|
|
<objective>
|
|
Establish the Tauri 2.x desktop wrapper foundation as a thin URL-loading shell. This is the
|
|
first vertical slice for DESK-01/DESK-02: install the missing toolchain, scaffold `apps/desktop`
|
|
in the monorepo, and deliver an app that launches, prompts for a server URL on first run, persists
|
|
it, and loads the configured Tessera web frontend in the native WebView.
|
|
|
|
Purpose: A user can run the desktop app and reach the live Tessera web app — the thinnest
|
|
end-to-end desktop experience. All native polish (tray, window state, autostart, notifications,
|
|
icon, production bundles) is layered on in Plan 06-02.
|
|
|
|
Output: A runnable `apps/desktop` Tauri project (`pnpm --filter=@tessera/desktop tauri dev`)
|
|
that connects to a user-configured server URL and remembers it.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
|
@$HOME/.claude/gsd-core/templates/summary.md
|
|
</execution_context>
|
|
|
|
<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
|
|
@pnpm-workspace.yaml
|
|
@apps/web/src/app/globals.css
|
|
</context>
|
|
|
|
<artifacts_this_phase_produces>
|
|
This plan introduces the following symbols/files (consumed by Plan 06-02):
|
|
- `apps/desktop/` — Tauri project root, pnpm package `@tessera/desktop`
|
|
- `apps/desktop/src-tauri/src/lib.rs::run()` — Rust entry function (extended in 06-02)
|
|
- `apps/desktop/src-tauri/tauri.conf.json` — central Tauri config (window, bundle, plugins)
|
|
- `apps/desktop/src-tauri/capabilities/default.json` — permission grants (extended in 06-02)
|
|
- `apps/desktop/src/setup.html` — first-run server URL page
|
|
- tauri-plugin-store `config.json` key `server_url` — persisted server URL contract
|
|
</artifacts_this_phase_produces>
|
|
|
|
<tasks>
|
|
|
|
<task type="checkpoint:human-verify" gate="blocking-human">
|
|
<name>Task 1: Install Tauri toolchain prerequisites (Rust + WebKitGTK dev headers)</name>
|
|
<read_first>
|
|
- .planning/phases/06-desktop-client-ci-cd/06-RESEARCH.md (Environment Availability table, Pitfall 1, Installation block)
|
|
</read_first>
|
|
<what-built>
|
|
RESEARCH.md confirms the machine is MISSING the Rust toolchain and `libwebkit2gtk-4.1-dev`
|
|
(plus likely libssl-dev, libxdo-dev, build-essential). These require `sudo apt` and a rustup
|
|
network installer — both need human authorization, so this is a blocking human-action checkpoint.
|
|
|
|
Execute these commands (the executor presents them; the human runs/authorizes sudo):
|
|
- apt install: `sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev`
|
|
- Rust: `curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh -s -- -y` then `source "$HOME/.cargo/env"`
|
|
</what-built>
|
|
<how-to-verify>
|
|
Run each and confirm a real version/path is printed (not "not found"):
|
|
1. `rustc --version` prints a version >= 1.77.2
|
|
2. `cargo --version` prints a version
|
|
3. `pkg-config --modversion webkit2gtk-4.1` prints a version (e.g. 2.x)
|
|
4. `pkg-config --exists libssl && echo OK` prints OK
|
|
</how-to-verify>
|
|
<acceptance_criteria>
|
|
- `rustc --version` exits 0 and prints version >= 1.77.2
|
|
- `pkg-config --modversion webkit2gtk-4.1` exits 0 (no "not found")
|
|
</acceptance_criteria>
|
|
<resume-signal>Type "approved" once all four checks print versions, or describe which failed</resume-signal>
|
|
</task>
|
|
|
|
<task type="auto" tdd="false">
|
|
<name>Task 2: Scaffold apps/desktop Tauri project as URL-loading wrapper</name>
|
|
<files>apps/desktop/package.json, apps/desktop/src-tauri/Cargo.toml, apps/desktop/src-tauri/tauri.conf.json, apps/desktop/src-tauri/build.rs, apps/desktop/src-tauri/src/main.rs, apps/desktop/src-tauri/src/lib.rs, apps/desktop/src-tauri/capabilities/default.json</files>
|
|
<read_first>
|
|
- .planning/phases/06-desktop-client-ci-cd/06-RESEARCH.md (Pattern 1 URL-Loading WebView, Recommended Project Structure, Cargo.toml Plugin Dependencies, Capabilities Configuration, Pitfall 6 monorepo scaffolding)
|
|
- pnpm-workspace.yaml (confirms apps/* glob already covers apps/desktop)
|
|
- apps/api/package.json (for name/version pattern: `@tessera/*`, version 0.0.1, private true)
|
|
</read_first>
|
|
<action>
|
|
Create `apps/desktop/` manually to avoid the monorepo scaffold confusion in Pitfall 6 (do NOT run
|
|
create-tauri-app at repo root). Set package.json name to `@tessera/desktop`, version `0.0.1`, private true,
|
|
with scripts `tauri` (runs `tauri`), `dev` (`tauri dev`), `build` (`tauri build`). Add devDependency
|
|
`@tauri-apps/cli@2.11.3` and dependencies `@tauri-apps/api@2.11.1` plus `@tauri-apps/plugin-store@2.4.3`.
|
|
Install via `pnpm add -D @tauri-apps/cli --filter=@tessera/desktop` and `pnpm add @tauri-apps/api @tauri-apps/plugin-store --filter=@tessera/desktop`.
|
|
|
|
Create `src-tauri/Cargo.toml` with package name `tessera-desktop`, edition 2021, a `[lib]` entry named
|
|
`tessera_desktop_lib` (crate-type cdylib + staticlib + rlib), build-dependency `tauri-build = "2"`, and
|
|
dependencies: `tauri = { version = "2", features = ["tray-icon"] }`, `tauri-plugin-store = "2"`,
|
|
`serde = { version = "1", features = ["derive"] }`, `serde_json = "1"`. Only the store plugin is wired in
|
|
this plan; notification/autostart/window-state are added in 06-02.
|
|
|
|
Create `src-tauri/build.rs` calling `tauri_build::build()`.
|
|
|
|
Create `src-tauri/tauri.conf.json`: `productName` "Tessera", `version` "0.0.1", `identifier`
|
|
"de.ctl.tessera.desktop". Under `build`, set `frontendDist` to `../src` (the local setup page directory) and
|
|
`devUrl` to `http://localhost:1420`. Under `app.windows`, one window: label "main", title "Tessera",
|
|
width 1280, height 800, center true, decorations true, resizable true. Set `app.withGlobalTauri` true.
|
|
Add `app.security.csp` allowing connection to the configured server (use `default-src 'self'; connect-src *`
|
|
for the URL-configurable wrapper per D-02). Under `bundle`, set `active` true, `targets` `["appimage", "nsis"]`,
|
|
`icon` `["icons/icon.png", "icons/icon.ico"]` (icons added in 06-02 — note this in a SUMMARY follow-up if build
|
|
is attempted before icons exist).
|
|
|
|
Create `src-tauri/src/main.rs` as the generated entry: `#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]`
|
|
then `fn main() { tessera_desktop_lib::run() }`.
|
|
|
|
Create `src-tauri/src/lib.rs` exposing `pub fn run()`. Register the store plugin via
|
|
`tauri_plugin_store::Builder::new().build()`. In a `.setup(|app| { ... })` callback, open the store
|
|
`config.json`, read key `server_url`; if present, get the `main` webview window and call `window.navigate(url)`
|
|
to load the configured server; if absent, leave the WebView on the bundled setup page. Use
|
|
`tauri::generate_context!()` in `.run(...)`.
|
|
|
|
Create `src-tauri/capabilities/default.json` with identifier "default", windows `["main"]`, permissions
|
|
`["core:default", "store:default"]` (additional permissions added in 06-02).
|
|
</action>
|
|
<verify>
|
|
<automated>test -f apps/desktop/src-tauri/tauri.conf.json && test -f apps/desktop/src-tauri/src/lib.rs && grep -q '@tessera/desktop' apps/desktop/package.json && grep -q 'store:default' apps/desktop/src-tauri/capabilities/default.json && cd apps/desktop/src-tauri && cargo check 2>&1 | grep -qiv 'could not find webkit'</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- `apps/desktop/package.json` name is `@tessera/desktop`
|
|
- `cargo check` inside `apps/desktop/src-tauri` completes without WebKitGTK "could not find" errors
|
|
- `tauri.conf.json` `frontendDist` is `../src` (not a hardcoded server URL)
|
|
- `lib.rs` reads `server_url` from store and calls `navigate` when present
|
|
</acceptance_criteria>
|
|
<done>cargo check passes; pnpm recognizes @tessera/desktop; config loads the local setup page as frontendDist</done>
|
|
</task>
|
|
|
|
<task type="auto" tdd="false">
|
|
<name>Task 3: First-run setup page — server URL input, validation, persistence, navigate</name>
|
|
<files>apps/desktop/src/setup.html</files>
|
|
<read_first>
|
|
- .planning/phases/06-desktop-client-ci-cd/06-RESEARCH.md (Pattern 3 Configurable Server URL, First-Run Setup Page code example, Security Domain — V5 Input Validation, malicious URL threat)
|
|
- apps/web/src/app/globals.css (OKLCH design tokens: --primary oklch(0.91 0.19 102), dark background oklch(0.17 0.01 260) — match setup page colors to the design system per D-06 spirit)
|
|
</read_first>
|
|
<action>
|
|
Create `apps/desktop/src/setup.html` as the local first-run page (D-02). It must:
|
|
- Render a centered card on dark background `oklch(0.17 0.01 260)` with a "Tessera" heading and a URL input
|
|
pre-filled with `http://localhost:3000`, plus a "Verbinden" button styled with primary `oklch(0.91 0.19 102)`.
|
|
All visible strings in German (response_language de): heading "Tessera", label "Server-URL eingeben:",
|
|
button "Verbinden", error text "Ungueltige URL" for invalid input.
|
|
- On submit, validate input with the `URL` constructor (per the established Phase 5 pattern, decision [05]
|
|
"URL constructor for client-side https-only validation"). Reject empty/malformed input; for non-https URLs
|
|
that are not localhost, show a warning string but allow (internal LAN servers may be http) — this mitigates
|
|
the malicious-URL Tampering threat T-06-01 (V5 input validation).
|
|
- On valid input, `import { load } from '@tauri-apps/plugin-store'`, `load('config.json', { autoSave: true })`,
|
|
`store.set('server_url', url)`, then `window.location.href = url` to navigate the WebView to the server.
|
|
- Use `<script type="module">` and rely on `withGlobalTauri`/the store plugin already permitted in capabilities.
|
|
</action>
|
|
<verify>
|
|
<automated>test -f apps/desktop/src/setup.html && grep -q "server_url" apps/desktop/src/setup.html && grep -q "new URL" apps/desktop/src/setup.html && grep -q "plugin-store" apps/desktop/src/setup.html</automated>
|
|
</verify>
|
|
<acceptance_criteria>
|
|
- setup.html validates the URL with `new URL(...)` before saving
|
|
- setup.html calls `store.set('server_url', ...)` and then navigates to the URL
|
|
- Visible strings are German ("Verbinden", "Server-URL eingeben:")
|
|
</acceptance_criteria>
|
|
<done>Entering a valid URL persists it to config.json and navigates the WebView; invalid input is rejected with a German error</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<threat_model>
|
|
## Trust Boundaries
|
|
|
|
| Boundary | Description |
|
|
|----------|-------------|
|
|
| user input → WebView navigation | Server URL typed by user controls where the WebView connects (untrusted text crosses into navigation) |
|
|
| local config store → app startup | Persisted `server_url` is read on launch and drives navigation |
|
|
|
|
## STRIDE Threat Register
|
|
|
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
|
|-----------|----------|-----------|-------------|-----------------|
|
|
| T-06-01 | Tampering | setup.html server URL input | mitigate | Validate with `new URL()` constructor; reject malformed; warn on non-https non-localhost (V5 input validation) |
|
|
| T-06-02 | Spoofing | WebView navigation target | accept | Internal-only tool; URL is user-chosen by design (D-02). CSP `connect-src *` required for configurable server. Locking to a single domain conflicts with the configurable-URL requirement; revisit at external-sales stage |
|
|
| T-06-03 | Information Disclosure | config.json stored URL | accept | URL is non-secret connection info stored via plugin-store (atomic, app-scoped path), not plaintext app config |
|
|
| T-06-SC | Tampering | npm/cargo installs | mitigate | All Tauri packages [Approved] in RESEARCH.md Package Legitimacy Audit; no [ASSUMED]/[SUS] blocking packages in this plan |
|
|
</threat_model>
|
|
|
|
<verification>
|
|
- `apps/desktop/src-tauri` `cargo check` passes (toolchain installed, config valid)
|
|
- `pnpm --filter=@tessera/desktop tauri dev` launches a window showing the setup page on first run
|
|
- Entering `http://localhost:3000` (with the dev stack running) navigates to the Tessera web app
|
|
- Relaunching the app skips setup and loads the stored URL directly
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Tauri toolchain installed and verifiable (rustc, webkit2gtk-4.1 pkg-config)
|
|
- `apps/desktop` exists as `@tessera/desktop` and `cargo check` passes
|
|
- First-run setup page persists the server URL and connects the WebView to it (DESK-02)
|
|
- No frontend assets bundled — `frontendDist` is the local setup page only (DESK-01 wrapper pattern)
|
|
</success_criteria>
|
|
|
|
<output>
|
|
Create `.planning/phases/06-desktop-client-ci-cd/06-01-SUMMARY.md` when done.
|
|
</output>
|