feat(desktop): URL editor, tray, Linux bundles, cookie fix (LC-123) #124

Merged
David merged 1 commit from feat/desktop-clients into main 2026-05-17 03:37:05 +02:00
Owner

Summary

Continues LC-123 (Tauri 2 desktop migration). Closes the biggest desktop usability gaps: the binary now ships with a URL editor on its welcome page (no more "set env var and restart"), a system tray icon, the notification plugin wired up for future server-side calls, and proper Linux .deb + .AppImage bundles. Also fixes a silent login bug where WebKit2GTK rejected the session cookie on http://localhost because of the unconditional Secure flag.

LC-123 has been reopened to In Progress with an audit comment listing what is now done vs. carved out into child issues LC-126..LC-132.

Changes

server/

  • state.rs: new AppState::cookies_secure() returning base_url.starts_with("https://"). Used to gate the Secure attribute on auth cookies so plain-HTTP dev (default http://localhost:8080 base URL) stops getting cookies silently dropped by WebKit2GTK.
  • routes/auth.rs, routes/two_factor.rs: thread secure: bool through all four cookie builders (build_session_cookie x2, build_pending_cookie, build_pending_registration_cookie) and pass state.cookies_secure() at every call site. Tests already construct AppState by hand and do not call the cookie builders directly, so no test changes were required.

desktop/

  • src/config.rs (new): persists the chosen server URL to dirs::config_dir()/lets-chat-desktop/config.json ($XDG_CONFIG_HOME on Linux, %APPDATA% on Windows, Application Support on macOS once that target lands). Precedence on startup: LETS_CHAT_SERVER_URL env var, then this config file, then the built-in default.
  • src/main.rs: adds the set_server_url IPC command, the Tauri 2 system tray (Show / Quit menu, left-click focuses the main window, right-click opens the menu), and registers tauri-plugin-notification so the lets-chat server pages can call invoke('plugin:notification|notify', { title, body }) once that server-side integration lands.
  • src/welcome.html: rewritten to include a URL editor with a blue "If you are running just dev-web-local, use http://localhost:18080" hint and a one-click fill, plus an always-visible status line so a click on "Save & connect" can never silently no-op. JS uses window.__TAURI_INTERNALS__.invoke directly (Tauri 2 injects only that surface; window.__TAURI__.core would require bundling the JS package) and falls back to a plain navigation if the IPC bridge is unavailable, so the user can always connect.
  • src/welcome.rs: server_reachable now treats HTTP 4xx/5xx as unreachable, not just network errors, so a wrong-port server returning a Problem+JSON 404 renders the welcome page instead of the raw JSON.
  • Cargo.toml: adds dirs, tauri-plugin-notification, and the tray-icon + image-png Tauri features.
  • capabilities/default.json: adds notification:default.
  • tauri.conf.json: enables bundling (bundle.active: true, targets: ["deb", "appimage"]), declares category SocialNetworking, icons, publisher, dependencies.
  • icons/icon-square-{32,64,128,256}.png (new): square padded versions of the existing icon.png (which is 256x143). The Tauri 2 AppImage bundler refuses to run without at least one square icon. The original icon.png is left untouched; replace these padded versions when a proper square logo asset is available.

ci-build/

  • Dockerfile.desktop-linux-bundles (new): installs tauri-cli 2.x and runs cargo tauri build --bundles deb,appimage to produce the bundles alongside the binary. Existing Dockerfile.desktop-linux is left alone so binary-only iteration stays fast.

justfile

  • build-desktop-linux and build-desktop-windows recipes: switched from nu shebang to bash because {{ docker_version_args }} contains $(git ...) substitutions that only bash evaluates; the previous nu shebang was baking the literal $(git rev-parse ...) string into the binary as GIT_HASH.
  • build-desktop-linux-bundles (new): builds via the bundle Dockerfile, extracts the binary plus the .deb and .AppImage into artifacts/.

.gitignore

  • artifacts/ (local bundle output).

LC-123 acceptance criteria progress

# AC Status after this PR Tracked
1 Builds on 5 targets PARTIAL: Linux + Windows already in main; macOS / Android / iOS carved out LC-126, LC-127, LC-128
2 LETS_CHAT_SERVER_URL controls target server DONE, plus GUI editor that persists to disk -
3 getUserMedia works on 5 targets NOT DONE LC-129
4 getDisplayMedia on Windows + macOS NOT DONE LC-130
5 OS notifications on 5 targets PARTIAL: plugin wired on desktop, server-side invoke calls pending; mobile gated by LC-127/LC-128 LC-127, LC-128
6 System tray on desktop DONE on Linux / Windows; macOS gated by LC-126 LC-126
7 CI bundles PARTIAL: local .deb+.AppImage works; CI workflow update LC-132; Windows .msi LC-131; macOS .dmg LC-126 LC-131, LC-132, LC-126
8 Self-updater ported DONE (already in main) -

Test plan

  • just check (fmt + standalone + saas + clippy)
  • just test (standalone, 53 binaries, all green)
  • just test-saas (saas, all green)
  • just build-desktop-linux produces a 17M binary with embedded GIT_HASH/GIT_VERSION (no more literal $(git ...) string)
  • just build-desktop-linux-bundles produces artifacts/bundle-linux/lets-chat_0.1.0_amd64.deb (5.4M) and lets-chat_0.1.0_amd64.AppImage (100M, includes bundled GTK/WebKit/glibc)
  • Manual: welcome page shows the URL editor + port-18080 hint, "Save & connect" persists to ~/.config/lets-chat-desktop/config.json and navigates on success, alert updates in place on failure
  • Manual: login flow works against just dev-web-local over http://localhost:18080 (was broken pre-cookie-fix)

Followups

The remaining LC-123 acceptance criteria are each tracked as subtask of LC-123:

  • LC-126: macOS desktop builds (Intel + ARM) and .dmg bundling
  • LC-127: Android desktop builds (.apk + .aab)
  • LC-128: iOS desktop builds (.ipa)
  • LC-129: getUserMedia permission hooks across all 5 platforms
  • LC-130: getDisplayMedia (screen share) for Windows + macOS
  • LC-131: Windows .msi bundle infrastructure
  • LC-132: CI workflow update to publish Linux .deb + .AppImage to the Generic Packages registry

LC-123 itself stays In Progress until those close or get explicitly deferred.

References: LC-123, LC-126, LC-127, LC-128, LC-129, LC-130, LC-131, LC-132.

## Summary Continues [LC-123](https://niceguyit.myjetbrains.com/youtrack/issue/LC-123) (Tauri 2 desktop migration). Closes the biggest desktop usability gaps: the binary now ships with a URL editor on its welcome page (no more "set env var and restart"), a system tray icon, the notification plugin wired up for future server-side calls, and proper Linux `.deb` + `.AppImage` bundles. Also fixes a silent login bug where WebKit2GTK rejected the session cookie on `http://localhost` because of the unconditional `Secure` flag. LC-123 has been reopened to In Progress with an audit comment listing what is now done vs. carved out into child issues LC-126..LC-132. ## Changes ### server/ - `state.rs`: new `AppState::cookies_secure()` returning `base_url.starts_with("https://")`. Used to gate the `Secure` attribute on auth cookies so plain-HTTP dev (default `http://localhost:8080` base URL) stops getting cookies silently dropped by WebKit2GTK. - `routes/auth.rs`, `routes/two_factor.rs`: thread `secure: bool` through all four cookie builders (`build_session_cookie` x2, `build_pending_cookie`, `build_pending_registration_cookie`) and pass `state.cookies_secure()` at every call site. Tests already construct `AppState` by hand and do not call the cookie builders directly, so no test changes were required. ### desktop/ - `src/config.rs` (new): persists the chosen server URL to `dirs::config_dir()/lets-chat-desktop/config.json` (`$XDG_CONFIG_HOME` on Linux, `%APPDATA%` on Windows, Application Support on macOS once that target lands). Precedence on startup: `LETS_CHAT_SERVER_URL` env var, then this config file, then the built-in default. - `src/main.rs`: adds the `set_server_url` IPC command, the Tauri 2 system tray (Show / Quit menu, left-click focuses the main window, right-click opens the menu), and registers `tauri-plugin-notification` so the lets-chat server pages can call `invoke('plugin:notification|notify', { title, body })` once that server-side integration lands. - `src/welcome.html`: rewritten to include a URL editor with a blue "If you are running `just dev-web-local`, use `http://localhost:18080`" hint and a one-click fill, plus an always-visible status line so a click on "Save & connect" can never silently no-op. JS uses `window.__TAURI_INTERNALS__.invoke` directly (Tauri 2 injects only that surface; `window.__TAURI__.core` would require bundling the JS package) and falls back to a plain navigation if the IPC bridge is unavailable, so the user can always connect. - `src/welcome.rs`: `server_reachable` now treats HTTP 4xx/5xx as unreachable, not just network errors, so a wrong-port server returning a Problem+JSON 404 renders the welcome page instead of the raw JSON. - `Cargo.toml`: adds `dirs`, `tauri-plugin-notification`, and the `tray-icon` + `image-png` Tauri features. - `capabilities/default.json`: adds `notification:default`. - `tauri.conf.json`: enables bundling (`bundle.active: true`, `targets: ["deb", "appimage"]`), declares category `SocialNetworking`, icons, publisher, dependencies. - `icons/icon-square-{32,64,128,256}.png` (new): square padded versions of the existing `icon.png` (which is 256x143). The Tauri 2 AppImage bundler refuses to run without at least one square icon. The original `icon.png` is left untouched; replace these padded versions when a proper square logo asset is available. ### ci-build/ - `Dockerfile.desktop-linux-bundles` (new): installs `tauri-cli` 2.x and runs `cargo tauri build --bundles deb,appimage` to produce the bundles alongside the binary. Existing `Dockerfile.desktop-linux` is left alone so binary-only iteration stays fast. ### justfile - `build-desktop-linux` and `build-desktop-windows` recipes: switched from `nu` shebang to `bash` because `{{ docker_version_args }}` contains `$(git ...)` substitutions that only bash evaluates; the previous nu shebang was baking the literal `$(git rev-parse ...)` string into the binary as `GIT_HASH`. - `build-desktop-linux-bundles` (new): builds via the bundle Dockerfile, extracts the binary plus the `.deb` and `.AppImage` into `artifacts/`. ### .gitignore - `artifacts/` (local bundle output). ## LC-123 acceptance criteria progress | # | AC | Status after this PR | Tracked | |---|---|---|---| | 1 | Builds on 5 targets | PARTIAL: Linux + Windows already in main; macOS / Android / iOS carved out | LC-126, LC-127, LC-128 | | 2 | `LETS_CHAT_SERVER_URL` controls target server | DONE, plus GUI editor that persists to disk | - | | 3 | `getUserMedia` works on 5 targets | NOT DONE | LC-129 | | 4 | `getDisplayMedia` on Windows + macOS | NOT DONE | LC-130 | | 5 | OS notifications on 5 targets | PARTIAL: plugin wired on desktop, server-side invoke calls pending; mobile gated by LC-127/LC-128 | LC-127, LC-128 | | 6 | System tray on desktop | DONE on Linux / Windows; macOS gated by LC-126 | LC-126 | | 7 | CI bundles | PARTIAL: local `.deb`+`.AppImage` works; CI workflow update LC-132; Windows `.msi` LC-131; macOS `.dmg` LC-126 | LC-131, LC-132, LC-126 | | 8 | Self-updater ported | DONE (already in main) | - | ## Test plan - [x] `just check` (fmt + standalone + saas + clippy) - [x] `just test` (standalone, 53 binaries, all green) - [x] `just test-saas` (saas, all green) - [x] `just build-desktop-linux` produces a 17M binary with embedded `GIT_HASH`/`GIT_VERSION` (no more literal `$(git ...)` string) - [x] `just build-desktop-linux-bundles` produces `artifacts/bundle-linux/lets-chat_0.1.0_amd64.deb` (5.4M) and `lets-chat_0.1.0_amd64.AppImage` (100M, includes bundled GTK/WebKit/glibc) - [x] Manual: welcome page shows the URL editor + port-18080 hint, "Save & connect" persists to `~/.config/lets-chat-desktop/config.json` and navigates on success, alert updates in place on failure - [x] Manual: login flow works against `just dev-web-local` over `http://localhost:18080` (was broken pre-cookie-fix) ## Followups The remaining LC-123 acceptance criteria are each tracked as `subtask of` LC-123: - LC-126: macOS desktop builds (Intel + ARM) and `.dmg` bundling - LC-127: Android desktop builds (`.apk` + `.aab`) - LC-128: iOS desktop builds (`.ipa`) - LC-129: `getUserMedia` permission hooks across all 5 platforms - LC-130: `getDisplayMedia` (screen share) for Windows + macOS - LC-131: Windows `.msi` bundle infrastructure - LC-132: CI workflow update to publish Linux `.deb` + `.AppImage` to the Generic Packages registry LC-123 itself stays In Progress until those close or get explicitly deferred. References: LC-123, LC-126, LC-127, LC-128, LC-129, LC-130, LC-131, LC-132.
feat(desktop): URL editor, tray, Linux bundles, cookie fix (LC-123)
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 1m1s
0f8e400418
Continue LC-123 (Tauri 2 migration). Land URL editor + persistence, system tray, notification plugin, Linux .deb and .AppImage bundles, and fix the Secure-cookie bug that broke desktop login on http:// URLs (WebKit2GTK rejects Secure cookies on plain HTTP, including localhost, so Chrome/Firefox-only localhost-secure-context exception did not save the desktop client).

Punch list of unaddressed acceptance criteria tracked at LC-126 (macOS + .dmg), LC-127 (Android), LC-128 (iOS), LC-129 (getUserMedia hooks), LC-130 (getDisplayMedia), LC-131 (Windows .msi), LC-132 (CI publish bundles).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
David merged commit f8fb7ba08b into main 2026-05-17 03:37:05 +02:00
David deleted branch feat/desktop-clients 2026-05-17 03:37:05 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/lets-chat!124
No description provided.