Files
waggle-os/docs/audits/2026-07-08-desktop-wrapper-t14-analysis.md
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

20 KiB

Desktop Wrapper T14 Analysis - 2026-07-08

Status: analysis supplement plus focused release-workflow, tray, packaged-startup, and sidecar hardening.

Purpose: deepen T14 evidence for the Tauri desktop wrapper, installer/update path, tray behavior, sidecar startup, and native-to-web UX bridge.

Sources Inspected

  • app/src-tauri/tauri.conf.json
  • app/src-tauri/capabilities/default.json
  • app/src-tauri/src/lib.rs
  • app/src-tauri/src/tray.rs
  • app/src-tauri/src/service.rs
  • app/src-tauri/Cargo.toml
  • app/package.json
  • app/tests/auto-update.test.ts
  • app/scripts/*.test.ts
  • apps/web/src/App.tsx
  • apps/web/src/lib/tauri-bindings.ts
  • apps/web/src/providers/ServiceProvider.tsx
  • .github/workflows/release.yml
  • scripts/check-sidecar-resources.mjs
  • scripts/build-sidecar.mjs
  • scripts/bundle-node.mjs
  • scripts/bundle-native-deps.mjs
  • scripts/stage-sidecar-deps.mjs

Guideline baseline: Vercel Web Interface Guidelines, fetched 2026-07-08 from https://raw.githubusercontent.com/vercel-labs/web-interface-guidelines/main/command.md.

Commands Run

npx tsc --noEmit --project app/tsconfig.json
npx vitest run app/tests/auto-update.test.ts app/scripts/signing-config.test.ts app/scripts/installer-config.test.ts app/scripts/bundle-runtimes.test.ts --reporter=dot
node scripts/check-sidecar-resources.mjs
cargo check --manifest-path app/src-tauri/Cargo.toml
npm run test -- tauri-bindings.test.ts adapter.tauri-branch.test.ts --reporter=dot
npx vitest run app/tests/e2e/startup.test.ts app/tests/e2e/chat.test.ts app/tests/e2e/workspaces.test.ts --reporter=dot
node -e "<parse tauri config/capability JSON files>"
app/src-tauri/resources/node.exe --version
npx vitest run packages/server/tests/tauri-config.test.ts --reporter=dot
npm run test -w apps/web -- src/lib/tauri-bindings.test.ts --reporter=dot
npx tsc --noEmit --project apps/web/tsconfig.json
npm run build:packages
npm run tauri:build:local
npx vitest run packages/server/tests/tauri-config.test.ts app/tests/auto-update.test.ts --reporter=dot
cargo test --manifest-path app/src-tauri/Cargo.toml service_script -- --nocapture
npx vitest run packages/server/tests/local/network-auth.test.ts --reporter=dot
node scripts/bundle-node.mjs
npx tauri build --debug --no-bundle
Start-Process app/src-tauri/target/debug/waggle.exe with isolated WAGGLE_DATA_DIR; poll http://127.0.0.1:3333/health

Command Results

Check Result UX meaning
app TypeScript Pass Desktop build/signing/installer scripts typecheck. Refreshed in continuation run.
Tauri helper/static tests Pass, 4 files / 85 tests Auto-update config, signing config, installer config, and runtime-bundling helpers are covered.
Sidecar resource preflight Pass Local resources include Node runtime, native deps, and staged node_modules; service.js exists. Refreshed in continuation run.
Rust cargo check Pass Tauri Rust shell compiles in dev profile, including the tray native quit/settings changes. Refreshed after tray hardening.
Web-side Tauri binding tests from apps/web Pass, focused binding 17/17 and prior binding/adapter bundle 19/19 IPC command wrappers, adapter Tauri branches, the desktop navigation bridge, and native service/update notice mapping are covered when run through the web package. Output includes punycode deprecation warnings. Refreshed after service/update event surfacing.
Root Vitest command for web-side Tauri tests Fails by command shape Root Vitest excludes apps/**; this is a verification-documentation gap, not a product runtime failure.
App service E2E Pass, 3 files / 11 tests Service startup, health, settings persistence, chat SSE, workspace/session, and memory-scope API flows work in the Vitest harness. Output is noisy with mock-embedding and unknown-model cost warnings.
JSON parse Pass Tauri config, build/dev overrides, and capability JSON parse.
Bundled Node runtime Pass app/src-tauri/resources/node.exe -p "process.version + ' abi=' + process.versions.modules" returns v22.22.2 abi=127 on this local build, matching the Node ABI that staged native deps. bundle-node.mjs now defaults to the current staging Node version, with WAGGLE_BUNDLED_NODE_VERSION available for an intentional pin.
Release workflow/tray/static desktop bridge guard Pass, 1 file / 25 tests packages/server/tests/tauri-config.test.ts now requires release builds to run npm run build:packages before each desktop sidecar bundle point, guards the tray menu against unsupported Pause/About/Quit-event actions, requires the web app to mount both desktop navigation and shell-event listeners, and guards updater-disabled startup plus Node ABI preflight behavior.
Web app TypeScript Pass npm run typecheck:web confirms the App-level desktop event bridge and Tauri binding types compile.
Updater-disabled static contract Pass, 2 files / 35 tests tauri.conf.json has updater config absent, updater capability is not exposed, and Rust no longer initializes tauri_plugin_updater while signed updater artifacts are missing.
Packaged debug binary build without installer Pass npx tauri build --debug --no-bundle builds app/src-tauri/target/debug/waggle.exe after sidecar/web resource preflight.
Full local debug bundle/MSI/NSIS Pass locally npm run tauri:build:local --prefix app completed and produced Waggle_0.2.0_x64_en-US.msi (150,135,110 bytes, 2026-07-10 18:59) and Waggle_0.2.0_x64-setup.exe (97,806,701 bytes, 2026-07-10 19:08). Both local debug artifacts are currently unsigned.
Packaged debug startup smoke Pass Launching target/debug/waggle.exe with an isolated WAGGLE_DATA_DIR reaches /health with status: ok, mode: local, database.healthy: true, serviceHealth.watchdogRunning: true; captured stderr contains no CORS: origin not allowed, ERR_MODULE_NOT_FOUND, or NODE_MODULE_VERSION errors.

Native Event Consumer Matrix

The tray-specific false affordances found in the first T14 pass are now narrowed: Pause Agents and About Waggle are no longer offered from the tray, Settings has a web bridge to the shipped /settings route, and Quit uses Tauri's native app.exit(0) path. The updater is intentionally disabled until signed updater artifacts exist, so update availability is future-ready in the web mapper but not emitted by the native shell in the current build. Remaining active native events are service-watchdog signals, not tray menu actions.

waggle://navigate
waggle://service-restart-needed
waggle://service-status
Native event / behavior Source Current consumer evidence UX reading
Tray icon click / Open Waggle app/src-tauri/src/tray.rs Native show/focus; no React consumer needed Source-wired, still needs packaged smoke.
Close window to tray app/src-tauri/src/lib.rs:135-140 Native hide on close; no React consumer needed Source-wired, still needs packaged smoke.
Ctrl+Shift+W global shortcut app/src-tauri/src/lib.rs Native toggle visibility; no React consumer needed Source-wired, still needs target-OS smoke.
Tray Settings app/src-tauri/src/tray.rs, apps/web/src/App.tsx, apps/web/src/lib/tauri-bindings.ts Rust shows/focuses the main window, emits waggle://navigate to /settings, and the web app mounts TauriDesktopEventBridge to route only shipped desktop destinations. Source-wired, still needs packaged smoke.
Tray Quit app/src-tauri/src/tray.rs Native app.exit(0) path; no React consumer needed. Source-wired to Tauri RunEvent::Exit, still needs packaged smoke.
Pause Agents tray action app/src-tauri/src/tray.rs Menu item and emitter removed. Hidden until there is real pause/resume behavior.
About Waggle tray action app/src-tauri/src/tray.rs, apps/web/src/App.tsx Menu item and /about emitter removed; no missing-route target remains. Hidden until there is a real About destination.
waggle://update-available apps/web/src/lib/tauri-bindings.ts, apps/web/src/App.tsx listenDesktopShellEvents() keeps a future Update available toast mapper, but Rust updater registration/emission is disabled while updater signing is not provisioned. Future-ready web mapping only; signed-update release-channel proof remains deferred.
waggle://service-status app/src-tauri/src/service.rs, apps/web/src/lib/tauri-bindings.ts, apps/web/src/App.tsx restarting maps to a reconnecting toast; failed maps to a destructive stopped-service recovery toast. Source/unit-wired to visible UI, still needs packaged watchdog smoke.
waggle://service-restart-needed app/src-tauri/src/service.rs, apps/web/src/lib/tauri-bindings.ts, apps/web/src/App.tsx listenDesktopShellEvents() maps restart-needed events to a visible local-service restart toast. Source/unit-wired to visible UI, still needs packaged watchdog smoke.

Continuation refresh:

rg -n -F "waggle://pause-agents" app/src-tauri apps/web/src -> no product emitter/listener
rg -n -F "waggle://navigate" app/src-tauri apps/web/src -> tray emitter + web bridge
rg -n -F "waggle://quit" app/src-tauri apps/web/src -> no product emitter/listener; tray uses app.exit(0)
rg -n -F "waggle://update-available" app/src-tauri apps/web/src -> web shell-event listener only; native updater emission disabled
rg -n -F "waggle://service-status" app/src-tauri apps/web/src -> Rust emitters + web shell-event listener
rg -n -F "waggle://service-restart-needed" app/src-tauri apps/web/src -> Rust emitter + web shell-event listener

Inference: Settings now has the frontend listener required for its delegated route behavior, Quit no longer delegates to React, service-watchdog signals now surface as visible toasts through the same desktop bridge, and update availability has a future-ready web mapper while native updater emission remains disabled. The packaged startup/smoke layer is now proven for app boot and sidecar health; tray, close-to-tray, shortcut, and forced watchdog-restart interactions still need targeted packaged interaction evidence.

What Is Proven Now

  • Static desktop TypeScript, Rust compilation, config JSON, resource staging, and service-level API flows are in good shape.
  • The active installed sidecar path is packages/server/src/local/service.ts -> scripts/build-sidecar.mjs -> app/src-tauri/resources/service.js -> app/src-tauri/src/service.rs.
  • scripts/check-sidecar-resources.mjs correctly prevents a raw Tauri build from silently omitting staged runtime resources.
  • ServiceProvider has a generic boot/reconnect path with three retries and broadcasts connect-settled state; several routed surfaces show "service unreachable" states.
  • Release workflow builds workspace packages, then stages Node, native deps, sidecar deps, and frontend before Tauri action builds Windows/macOS draft release artifacts.
  • Native tray click/Open, close-to-tray, and the global shortcut are implemented in Rust rather than delegated to missing web listeners.
  • Tray Settings is source-wired through a Tauri desktop navigation bridge to /settings; unsupported /about navigation is no longer emitted.
  • Tray Quit is source-wired through Tauri app.exit(0), so the existing RunEvent::Exit sidecar cleanup path is reachable from the menu.
  • Pause Agents and About Waggle are no longer exposed as tray actions until there is real product behavior behind them.
  • Native service-watchdog events now have React consumers, and the future update event mapper is ready: waggle://update-available, waggle://service-status, and waggle://service-restart-needed map to concise toasts, guarded by tauri-bindings.test.ts and the static desktop bridge test.
  • Packaged debug startup now works locally: the Tauri shell creates the tray icon, starts the bundled sidecar from target/debug/resources/service.js, reaches /health, and reports healthy database plus running watchdog.
  • Startup blockers found and fixed by packaged smoke: invalid plugins.dialog config, updater plugin initialization without updater config, debug-sidecar source path resolution, bundled Node/native ABI mismatch, and Tauri webview http://tauri.localhost CORS rejection.

Still Not Proven

  • Packaged debug app launch and sidecar health on Windows are proven locally; clean installed MSI/NSIS launch on Windows/macOS remains unproven.
  • Published release artifact availability and signed-installer trust are still not proved; the local debug MSI/NSIS artifacts are present but Get-AuthenticodeSignature reports NotSigned for both.
  • Actual packaged tray menu behavior for Open, Settings, and Quit.
  • Close-to-tray behavior in the installed binary.
  • Ctrl+Shift+W global shortcut behavior on target OS.
  • Packaged-app proof that forced watchdog events produce the expected visible toasts.
  • Port-conflict recovery in the installed app.
  • Installer warning/trust experience for unsigned, self-signed, or properly signed channels.
  • Auto-update user experience. The updater config, capability, and Rust runtime registration are intentionally disabled for v1 until signed updater artifacts exist; the web mapper remains future-ready.

Line-Level Findings

ID Finding Evidence Correction
T14-1 Service app-level events previously had no React consumers, and update mapping was not future-ready. Current tauri-bindings.ts maps waggle://update-available, waggle://service-status, and waggle://service-restart-needed to toast notices, and App.tsx mounts listenDesktopShellEvents() through TauriDesktopEventBridge; guarded by focused binding tests and tauri-config.test.ts. Native update emission remains disabled until signed updater artifacts exist. Focused fixed locally for service events and future update mapping; packaged smoke still needs to prove real service-watchdog events produce visible toasts.
T14-2 About tray action previously targeted a route that does not exist. Current tray.rs no longer includes About Waggle or /about; guarded by tauri-config.test.ts. Focused fixed locally by removing the unsupported tray action.
T14-3 Quit tray action previously delegated to an unconsumed web event. Current tray.rs uses app.exit(0); guarded by tauri-config.test.ts and cargo check. Focused fixed locally; packaged smoke still needs to prove sidecar cleanup through the real menu.
T14-4 Update UX is intentionally disabled and must not break startup. app/tests/auto-update.test.ts and packages/server/tests/tauri-config.test.ts require updater config absent, updater capability not exposed, no UpdaterExt import, no updater plugin registration, and no startup .updater() check. Packaged smoke originally panicked on plugins.updater: null; the current binary no longer does. Keep update UI hidden/deferred, or re-enable signed updater artifacts and visible update handling end to end.
T14-5 Generic service reconnect exists, and native watchdog events now surface through the desktop bridge. ServiceProvider.tsx still owns generic reconnect, while listenDesktopShellEvents() turns native watchdog status/restart events into visible recovery toasts. Focused fixed locally; installed-app watchdog failure/restart proof remains.
T14-6 Web-side Tauri tests are not discoverable from the root Vitest command. Root command exits "No test files found" because root config excludes apps/**; running from apps/web passes 2 files / 19 tests. Document the correct web-package command in the final verification lane or align root test discovery.
T14-7 Installed-app UX still lacks real rendered evidence. Current command evidence is source/static/API-level only. Capture packaged app startup, tray, close-to-tray, shortcut, service recovery, and installer trust evidence before a full 9/10 claim.
T14-8 Release packaging previously skipped the package build step used by the PR Tauri verification lane. Historical .github/workflows/release.yml went from npm install directly to sidecar bundling; current workflow runs npm run build:packages before both Windows and macOS sidecar bundle steps, guarded by tauri-config.test.ts. Focused fixed locally; full release closure still needs signed/public artifacts and installed-app smoke.
T14-9 Settings tray action previously emitted an unconsumed route event. Current App.tsx mounts TauriDesktopEventBridge; tauri-bindings.ts listens for waggle://navigate and accepts only /settings; focused web binding tests pass 15/15. Focused fixed locally; packaged smoke still needs to prove the real tray menu reaches Settings.
T14-10 Packaged startup previously panicked before the UI could load. Direct debug-exe smoke exposed plugins.dialog object deserialization and updater null deserialization panics. Current config removes plugins.dialog; current Rust does not register updater while config is absent; static tests guard both. Fixed for debug packaged startup; installer artifact smoke remains.
T14-11 Packaged sidecar previously launched the dev service.ts path from the wrong root. Smoke exposed ERR_MODULE_NOT_FOUND for D:\packages\server\src\local\service.ts. service.rs now prefers bundled resources/service.js when present, falls back to ancestor-searched dev source only when needed, and has Rust unit tests for both paths. Fixed; packaged startup smoke reaches /health.
T14-12 Bundled Node and staged native deps could silently have incompatible ABIs. Smoke exposed better_sqlite3.node built for ABI 127 running under bundled Node ABI 115. bundle-node.mjs now defaults to the current staging Node version; check-sidecar-resources.mjs fails on ABI mismatch before packaging. Fixed locally; CI remains aligned because CI stages under Node 20 unless intentionally changed.
T14-13 Tauri webview health calls could be rejected by local CORS. Packaged logs showed CORS: origin not allowed before allowing http://tauri.localhost; network-auth.test.ts now covers Tauri webview localhost origins. Fixed; current packaged smoke has no CORS rejection in stderr.

Correction Decision

Do not treat all tray items equally:

  1. Keep Rust-native handling for Open, close-to-tray, and Ctrl+Shift+W, then verify them in a packaged smoke.
  2. Keep Settings as the only delegated tray route and route it through the tested desktop navigation bridge.
  3. Keep Quit native through app.exit(0) so sidecar cleanup is reachable.
  4. Keep Pause Agents and About Waggle hidden until they have real product behavior.
  5. Keep service watchdog events surfaced through accessible toasts, keep update mapping future-ready while updater is disabled, then prove watchdog behavior in a packaged smoke.

T14 Acceptance

T14 remains open until either:

  1. Desktop wrapper/release UX is explicitly deferred from the five-persona score, or
  2. Evidence proves all of the following:
  • Packaged app launches and reaches Home or a clear service-recovery screen. Current debug smoke proves sidecar /health; rendered Home still needs packaged visual proof.
  • Tray Open, Settings, and Quit actions are proved in a packaged smoke; Pause and About remain intentionally removed until implemented.
  • Close-to-tray and Ctrl+Shift+W are verified on a target OS lane.
  • Sidecar startup, port conflict, crash/restart, and restart-needed states are visible and recoverable.
  • Installer/signing expectations are documented for the actual release channel.
  • Update UX is either fully signed and user-visible or intentionally disabled without misleading UI.

Phase Impact

This does not change Phase 1. T14 remains a Phase 2/Launch final-product gate after in-app P0 blockers are cleared, unless a Phase 1 verification command directly requires a small supporting fix.