Skip to content

feat(desktop): port tray/Tauri integration onto the client/server settings model - #216

Merged
elasticdotventures merged 19 commits into
mainfrom
feat/tray-client-server-port
Aug 31, 2026
Merged

feat(desktop): port tray/Tauri integration onto the client/server settings model#216
elasticdotventures merged 19 commits into
mainfrom
feat/tray-client-server-port

Conversation

@elasticdotventures

Copy link
Copy Markdown
Member

Summary

Supersedes PR #213 (closed in favor of this one — #213 was built against a pre-client/server SettingsStore snapshot of main and doesn't apply cleanly on top of the settings-service refactor that landed independently while that work was in progress).

Same functional goal as #213host-tauri.exe's Windows tray previously had no toast/notification support at all (a minimal Show/Exit-only stub), while a separate standalone host-tray.exe had the full feature set but no integration with the main app. This ports that consolidation onto the SettingsClient/ledgrrr-service architecture already on main.

  • Made the tray's "show window" action injectable instead of hardcoding a spawn of a separate process
  • Wired host-tauri.exe's Windows tray through the same shared, tested tray::runtime::run() the standalone tray used, now taking a SettingsClient — gives it the full toggle/notification/toast menu, fixing the reported "toast notifications don't work" bug
  • Replaced the BurntToast PowerShell module dependency with a native windows-rs toast implementation (notify::native::NativeToastNotifier, wrapping the already-present but previously-unwired notification::windows_toast::ToastNotifier) — no external process, no module install required. NotificationBackend::PowerShell renamed to Native (serde alias preserves settings persisted under the old name)
  • Retired host-tray.exe as a build target (source kept under src/bin/legacy/ for reference; host-window.exe/Slint were already retired on main via refactor(ledgerr-host): remove legacy Slint host #174)
  • Fixed a real bug found during live validation: "Show Window" didn't un-minimize a minimized window (SW_SHOW vs SW_RESTORE)
  • Fixed a real bug: any tray command error used to kill the whole app silently (exit code 0, no diagnostics) — now logged and the loop continues; only Quit ends it
  • Fixed a real robustness gap found live on this branch specifically: with ledgrrr-service not running (the realistic first-launch state), the app previously either defaulted the main window to hidden/inert states or — worse — silently skipped tray setup entirely, so no tray icon ever appeared. Both main.rs's window-visibility check and its enable_tray check now log and fall back to a sensible default (visible; tray enabled) instead. Verified live: tray icon now appears with the settings service fully down.
  • Fixed the same registry-key-leak issue in two places (ledgrrr-settings, ledgerr-desktop-agent): SettingsStore::new()'s Windows registry backend ignores its path argument and always targets one fixed production key — correct for real callers, but meant every test using a tempdir path shared that one global mutable key. Added SettingsStore::with_backend() so tests can inject an isolated JsonFileBackend explicitly.
  • Grouped the four notification-event toggles into a "Notify me for" submenu; removed dead code

Test plan

  • cargo test -p ledgerr-host -p ledgrrr-settings --lib --tests --bins — 95 passing. 2 known pre-existing failures in chat_tools (Windows-path-in-TOML escaping bug tied to this machine's username) confirmed to fail identically on a clean, unmodified main — not introduced by this branch.
  • Live launch of host-tauri.exe with ledgrrr-service not running: main window appears (visible-by-default fallback), tray icon appears (L3dg3rrTrayWindow confirmed via window enumeration) with the full menu, not just Show/Exit
  • BurntToast module confirmed not installed on the validation machine; notify-test.exe --backend native returns a real "status": "ready"/"message": "toast sent" result via the native windows::UI::Notifications API

🤖 Generated with a build+review process (implementation, live validation against a running host-tauri.exe, iterative fixes based on what that surfaced).

https://claude.ai/code/session_015kUr7Kf9wN15TPqDiKsiKw

Comment on lines +30 to +34
impl Default for FoundryInstallActionArgs {
fn default() -> Self {
Self { approved: false }
}
}
Comment on lines +30 to +34
impl Default for FoundryInstallActionArgs {
fn default() -> Self {
Self { approved: false }
}
}
};
let json = serde_json::to_string(&status).unwrap();
let back: FoundryLocalStatus = serde_json::from_str(&json).unwrap();
assert_eq!(back.cli_found, true);
let json = serde_json::to_string(&status).unwrap();
let back: FoundryLocalStatus = serde_json::from_str(&json).unwrap();
assert_eq!(back.cli_found, true);
assert_eq!(back.service_running, false);
promptexecutionerr and others added 15 commits August 30, 2026 09:00
…erShell; retire host-tray.rs

- NotificationBackend::PowerShell renamed to Native (serde alias keeps
  reading settings persisted under the old name) — the actual toast
  delivery is being replaced with native windows-rs calls in ledgerr-host,
  this crate only owns the shared data types.
- SettingsStore::with_backend(path, backend) added alongside new(), so
  tests can construct an isolated JsonFileBackend directly instead of
  going through Windows registry auto-selection — new()'s registry
  backend ignores its path argument entirely and always targets one
  fixed production key, which is correct for real callers but means any
  test using new() over a tempdir shares that one global mutable key
  with every other test and any real running instance.
- crates/ledgerr-host/src/bin/host-tray.rs moved to src/bin/legacy/ as
  reference-only (git mv staged this alongside the above; it's the first
  step of retiring the standalone host-tray.exe binary — completed in
  the following commits, which remove its [[bin]] entry and repoint
  host-tauri's tray at the same shared runtime it used).
…le host-tray refs

settings_server.rs's tests hit the same registry-auto-selection issue
fixed in ledgrrr-settings — repoint them at SettingsStore::with_backend
+ JsonFileBackend explicitly. status.rs/service_control.rs still
listed host-tray as a tray-binary candidate; it's being retired in
favor of host-tauri.exe, which already sorts first in that list.
…ndows-rs toasts

The tray's toast notifications shelled out to powershell.exe running
Import-Module BurntToast; New-BurntToastNotification — requiring a
separately-installed PowerShell module on every machine.

A native windows::UI::Notifications-based ToastNotifier already existed
in this crate (notification::windows_toast) but was never wired into
the tray's actual notification path — only its own unused tests
exercised it. Added notify::native::NativeToastNotifier, implementing
the same notify::types::Notifier trait the tray already consumes,
wrapping that existing native implementation (and the stderr fallback
on non-Windows) instead of shelling out to any external process.
…ubmenu

native.rs: drop the unused push_check helper, DYNAMIC_TEXT_IDS and
CHECK_ITEM_IDS constants, and fix a few compiler-flagged unused-mut/
unused-must-use warnings.

Group the four notification-event toggles (approval/submitted/failed/
completed) into a "Notify me for" submenu instead of sitting flat
among the other 10 top-level menu items — cuts the flat menu from 14
items to 11 and keeps the related settings together.
… down settings service

Ports this session's earlier work (originally built against a
SettingsStore this branch predates) onto the client/server settings
architecture already on main (SettingsClient, ledgrrr-service):

- TrayCommand::ShowWindow's action is now an injected closure instead
  of a hardcoded spawn of a separate process, so the same run()/
  handle_command can be reused by host-tauri (which shows its own
  webview) without any dependency on a standalone binary.
- Collapsed 7 near-identical toggle handlers into negate()/
  apply_toggle() helpers.
- Any single command's error (most realistically: ledgrrr-service isn't
  running yet) is now logged and the loop continues, instead of
  propagating via `?` out of run() and killing the whole tray — and, via
  host-tauri's exit-on-return, the whole app — over a transient failure.
  Only an explicit Quit ends the loop.
- run()'s own initial settings load falls back to defaults on failure
  instead of erroring out before ever creating the tray icon at all —
  confirmed live: with no settings service running, the tray now still
  appears (previously it silently never initialized).
- NotificationBackend::PowerShell -> Native throughout.
…ost-tray.exe

Replaces the minimal Show/Exit-only stub with the shared
tray::runtime::run(), injecting "show/unminimize/focus the Tauri
webview" as the window-show action. host-tauri now has the full
toggle/notification/toast menu that was previously only in the
standalone host-tray.exe, fixing the toast-notifications-never-wired-up
bug on the main app's tray.

Also: main.rs's window-visibility check and enable_tray check both
silently fell back to unhelpful defaults (or, for enable_tray, silently
skipped tray setup entirely) whenever the settings load failed —
exactly the case when ledgrrr-service isn't running yet. Both now log
and use an explicit, sensible default (visible; tray enabled) instead.
Removes its [[bin]] entry now that host-tauri.exe has full feature
parity (previous commit). Source stays at src/bin/legacy/host-tray.rs
for reference; it no longer compiles as part of any normal build.
host-tray no longer exists as a build target (previous commit).
PR #187 (spike/sysml-v2-parser-roundtrip) found holon-viz's SysmlV2Emitter
produced invalid SysML v2 (comment-swallowed closing brace, `block def`
instead of `part def`). Both bugs were independently fixed on main in PR
#183 (c106a5e) before this doc landed. Cherry-picks only the investigation
doc; the throwaway sysml-v2-parser-spike crate stays on the closed PR's
branch since its regression test now asserts stale (pre-fix) behavior.
@elasticdotventures
elasticdotventures force-pushed the feat/tray-client-server-port branch from 1176a46 to 1cac1be Compare August 30, 2026 09:00
This PR's own with_backend()/JsonFileBackend fix for the registry-leak
issue (SettingsStore::new's Windows registry backend ignores its path
arg and always targets one fixed production key) landed in
ledgrrr-settings and ledgerr-desktop-agent, but missed 3 test files in
ledgerr-host itself — the crate that actually owns the tray's real
SettingsStore usage:

- tests/settings_roundtrip.rs (5 tests)
- tests/settings_atomicity.rs (2 tests)
- tests/tray_wiring_smoke.rs (tray_enable_roundtrips_through_store)

On a real Windows box these all still called SettingsStore::new(tempdir)
directly, silently opening the one production registry key instead of an
isolated per-test backend — the exact corruption/cross-contamination bug
this PR claims to have fixed elsewhere, and in the worst case
(tray_enable_roundtrips_through_store) capable of writing enable_tray:
false into a real running host-tauri.exe's live settings from `cargo
test`. CI never caught it: the only Windows job runs a packaging smoke
test, not `cargo test`, and on Linux the registry backend is compiled out
entirely so these pass cleanly via the (correctly path-scoped)
JsonFileBackend fallback regardless.

Applied the same with_backend(path, JsonFileBackend::new(path)) pattern
already used in ledgrrr-settings/src/store.rs and
ledgerr-desktop-agent/src/settings_server.rs.

Verified: cargo test -p ledgerr-host --tests — 112 passed, 0 failed.
@elasticdotventures

Copy link
Copy Markdown
Member Author

Reviewed and pushed a fix directly to this branch (commit a10e402) — not merging yet, pending CI on the fixed commit and your own pass over the follow-up notes below.

Fixed: this PR's own registry-key test-isolation fix (SettingsStore::with_backend() + explicit JsonFileBackend, to stop tests from sharing the one fixed production Windows registry key) landed in ledgrrr-settings and ledgerr-desktop-agent, but missed 3 test files in ledgerr-host itself — the crate that actually owns the tray's real SettingsStore usage: tests/settings_roundtrip.rs (5 tests), tests/settings_atomicity.rs (2 tests), tests/tray_wiring_smoke.rs::tray_enable_roundtrips_through_store. On a real Windows box these still opened the production registry key directly — in the worst case, tray_enable_roundtrips_through_store could write enable_tray: false into a real running host-tauri.exe's live settings from cargo test, which is exactly the failure mode this PR set out to fix. CI never caught it because the only Windows job runs a packaging smoke test (not cargo test), and on Linux the registry backend is compiled out entirely so these passed cleanly regardless. Applied the same with_backend/JsonFileBackend pattern already used elsewhere in this PR; verified cargo test -p ledgerr-host --tests — 112 passed, 0 failed.

Separately: this branch was also stale (based on a pre-Containerfile-fix main), which is why test-and-build showed one green/one red run on the same commit earlier — rebased onto current main before the above fix.

Confirmed working, no changes needed:

  • SW_SHOW → un-minimize fix (tray.rs) — correct, clears the iconic bit SW_SHOW doesn't touch.
  • Tray command loop no longer killing the app on error (tray/runtime.rs) — correct, has a dedicated regression test.
  • Tray-icon-appears-with-service-down fallback (main.rs) — correct for the first-launch case described (note: doesn't re-sync if the service comes up later, but that's not what was claimed).

Not fixed — worth a follow-up issue, not a blocker:

  • tray/native.rs's submenu lookup by numeric position (GetSubMenu(hmenu, 7)) is only guarded by debug_assert_eq!, which compiles out in release builds — if the menu layout ever drifts, checkboxes could silently apply to the wrong item with zero detection in the shipped build.
  • The native WinRT toast path (notification/windows_toast.rs) has no CoInitializeEx/RoInitialize anywhere in the crate, and this PR is what first wires it into an always-on production path running on a background thread (tray.rs's spawned thread) rather than the notify-test.exe binary's own main thread this PR's live validation actually used. If WinRT activation needs an initialized COM apartment on that specific thread, the PR's "fixes toast notifications don't work" claim may not fully hold on all machines — worth explicitly verifying toasts fire from the tray's real thread, not just from notify-test.exe.
  • crates/ledgerr-desktop-agent/src/foundry_install_plan.rs + lifecycle.rs (a new Foundry Local install/lifecycle feature, including a new scxml SCXML-export dependency) is bundled into this PR but isn't mentioned anywhere in its title/description — doubles the diff size and makes "what did this PR change" harder to answer later. Not broken, just should've been a separate PR next time.

Please re-check CI on the new head commit before merging.

promptexecutionerr and others added 3 commits August 30, 2026 20:10
- Restore docs/superpowers/specs/2026-08-29-tray-tauri-integration-design.md
  and its companion plan doc from the superseded feat/windows-desktop-dogfood
  branch — Justfile referenced the spec doc, but it was never ported when
  this branch superseded that one. Retrieved verbatim, status line updated
  to note the branch handoff.
- Add invoke_reports_blocked_on_non_windows_even_when_approved, gated
  #[cfg(not(windows))] — exercises the untested !plan.executable_now
  branch in foundry_install_plan.rs::invoke() on non-Windows CI targets.
  Deliberately excluded from Windows builds: on Windows, executable_now
  is always true, so calling invoke(approved: true) there would reach the
  real winget-spawning code path as a side effect of running the test
  suite — the existing invoke_without_approval_never_launches test already
  covers the approval gate safely on every target.
…-store + foundry_install_plan conflicts

- crates/ledgrrr-settings/src/store.rs: this branch and #234 (already
  merged) independently fixed the same bug (SettingsStore::new's Windows
  registry backend ignoring its path argument in tests) with different
  APIs -- this branch's with_backend(path, backend) and #234's
  new_json_file(path). Kept both: with_backend stays the general
  primitive (used as-is by 4 other test call sites in this branch),
  new_json_file is now a thin wrapper around it, so neither PR's
  capability is lost.
- crates/ledgerr-desktop-agent/src/foundry_install_plan.rs: add/add
  conflict from divergent history (this branch and #235, also merged,
  each added the file); both sides already had identical content
  including the same non-Windows-only test, so no real merge was needed.
@elasticdotventures
elasticdotventures merged commit 75c19d5 into main Aug 31, 2026
13 checks passed
@elasticdotventures
elasticdotventures deleted the feat/tray-client-server-port branch August 31, 2026 05:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants